mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
132 Commits
v4.2.56-De
...
master-arq
Author | SHA1 | Date | |
---|---|---|---|
|
fcbd5f96ab | ||
|
4b486900ad | ||
|
8bd6403cd1 | ||
|
65b9079337 | ||
|
818e05b425 | ||
|
c5168f030f | ||
|
387ea019bd | ||
|
a981f83bb9 | ||
|
749a859db5 | ||
|
659c58b307 | ||
|
0b9ec7460b | ||
|
8b8e9e4919 | ||
|
bef4b2ec35 | ||
|
9333beda49 | ||
|
46289fa78c | ||
|
b3cafd126e | ||
|
c55fa9f9d2 | ||
|
210d96a3e3 | ||
|
42f3c7c128 | ||
|
6cd7002e91 | ||
|
085762e800 | ||
|
2db2b0ecc6 | ||
|
fd58a749ef | ||
|
3bddf4dfd1 | ||
|
e0e965f81c | ||
|
f2be3aeac3 | ||
|
a74d85e895 | ||
|
df2e651217 | ||
|
36d139268d | ||
|
e3ac3a8ddf | ||
|
415e756020 | ||
|
f754fe2fbf | ||
|
399030d8ae | ||
|
4c7e11ddc1 | ||
|
495bbf66bf | ||
|
2950ef010a | ||
|
c2eb5f23b4 | ||
|
94648ead28 | ||
|
ec0499e9da | ||
|
9b75026eef | ||
|
6036ff9b15 | ||
|
6476a7aac2 | ||
|
0edf34bfe2 | ||
|
aa1f25fc72 | ||
|
b44bebc1c6 | ||
|
1a17cdb62f | ||
|
7ce1618a9c | ||
|
4192c1f5a3 | ||
|
c2fcdddd1f | ||
|
f19db3aa5c | ||
|
e6a40e622c | ||
|
b3251e89d7 | ||
|
ae2bbbf668 | ||
|
96a46f36c2 | ||
|
10c59028e1 | ||
|
ab1356d070 | ||
|
70d4c016ef | ||
|
efe96d1d84 | ||
|
5d17f247de | ||
|
7c41ec7c25 | ||
|
9c5e48c3d5 | ||
|
2eb0f48994 | ||
|
1bdb05a471 | ||
|
c2fa76c76a | ||
|
35fd912c60 | ||
|
dfba6c6fc1 | ||
|
f3cb263aaa | ||
|
ec6e730559 | ||
|
196a27a27a | ||
|
99b6ca025a | ||
|
db555e8820 | ||
|
0d679ad993 | ||
|
dd80260781 | ||
|
08f41415dc | ||
|
2f65d4347e | ||
|
08c00d275d | ||
|
1888073dc2 | ||
|
6dd175f63b | ||
|
f83be05220 | ||
|
cade89ba16 | ||
|
277825c530 | ||
|
95fe7aafe0 | ||
|
f99deb4276 | ||
|
f5d4dcb97c | ||
|
99521f8a3f | ||
|
fcf05206b4 | ||
|
cc3aba1feb | ||
|
91d36ae909 | ||
|
98af5c45ed | ||
|
d3eceb6d6c | ||
|
79eaaa85fa | ||
|
0f5b8adb6b | ||
|
da8faa808b | ||
|
7311f2ce64 | ||
|
7e4f9eb0e1 | ||
|
1f5eafbcca | ||
|
7e16ec7088 | ||
|
912646f8ff | ||
|
dd309a3705 | ||
|
5376c9f410 | ||
|
b1d110ecbf | ||
|
5b7801ca6c | ||
|
aca9c9c941 | ||
|
6f94cd71ab | ||
|
1827a11f83 | ||
|
0023ce3356 | ||
|
7b9f3ae08d | ||
|
5e50731a78 | ||
|
df6c511d1d | ||
|
4bfd4b1a12 | ||
|
4a3f3d0f34 | ||
|
f0d1909d9f | ||
|
daf6799ac1 | ||
|
b7a010f904 | ||
|
d1518b8af0 | ||
|
39a85903ce | ||
|
d72474cd8f | ||
|
941e74beaf | ||
|
e618b91900 | ||
|
dcab5a0e72 | ||
|
1901d9547e | ||
|
7388d14bab | ||
|
2da28ad2db | ||
|
06bd80438e | ||
|
cd15eed005 | ||
|
23d0158804 | ||
|
2e9e614ad5 | ||
|
64b1de08be | ||
|
34c3d10767 | ||
|
f2eb64fd21 | ||
|
a80b16acba | ||
|
b1f5e9f48c |
@@ -30,6 +30,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
|
||||
* Fred Decker, Gregor Baues, Anthony W - Dayton
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -78,6 +79,12 @@ 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();
|
||||
// Set up MotorDrivers early to initialize all pins
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
|
||||
DISPLAY_START (
|
||||
// This block is still executed for DIAGS if display not in use
|
||||
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
||||
@@ -89,26 +96,19 @@ void setup()
|
||||
// Start Ethernet if it exists
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // WIFI_ON
|
||||
#else
|
||||
// ESP32 needs wifi on always
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
// 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
|
||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
DCC::begin();
|
||||
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
|
3
DCC.cpp
3
DCC.cpp
@@ -60,8 +60,7 @@ const byte FN_GROUP_5=0x10;
|
||||
FSH* DCC::shieldName=NULL;
|
||||
byte DCC::globalSpeedsteps=128;
|
||||
|
||||
void DCC::begin(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
void DCC::begin() {
|
||||
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Load stuff from EEprom
|
||||
|
5
DCC.h
5
DCC.h
@@ -51,7 +51,10 @@ const byte MAX_LOCOS = 30;
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const FSH * motorShieldName);
|
||||
static inline void setShieldName(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
};
|
||||
static void begin();
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
|
@@ -351,7 +351,7 @@ void DCCACK::callback(int value) {
|
||||
|
||||
switch (callbackState) {
|
||||
case AFTER_READ:
|
||||
if (ackManagerRejoin && autoPowerOff) {
|
||||
if (ackManagerRejoin && !autoPowerOff) {
|
||||
progDriver->setPower(POWERMODE::OFF);
|
||||
callbackStart=millis();
|
||||
callbackState=WAITING_30;
|
||||
|
102
DCCEXParser.cpp
102
DCCEXParser.cpp
@@ -25,6 +25,79 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
List of single character OPCODEs in use for reference.
|
||||
|
||||
When determining a new OPCODE for a new feature, refer to this list as the source of truth.
|
||||
|
||||
Once a new OPCODE is decided upon, update this list.
|
||||
|
||||
Character, Usage
|
||||
/, |EX-R| interactive commands
|
||||
-, Remove from reminder table
|
||||
=, |TM| configuration
|
||||
!, Emergency stop
|
||||
@, Reserved for future use - LCD messages to JMRI
|
||||
#, Request number of supported cabs/locos; heartbeat
|
||||
+, WiFi AT commands
|
||||
?, Reserved for future use
|
||||
0, Track power off
|
||||
1, Track power on
|
||||
a, DCC accessory control
|
||||
A,
|
||||
b, Write CV bit on main
|
||||
B, Write CV bit
|
||||
c, Request current command
|
||||
C,
|
||||
d,
|
||||
D, Diagnostic commands
|
||||
e, Erase EEPROM
|
||||
E, Store configuration in EEPROM
|
||||
f, Loco decoder function control (deprecated)
|
||||
F, Loco decoder function control
|
||||
g,
|
||||
G,
|
||||
h,
|
||||
H, Turnout state broadcast
|
||||
i, Reserved for future use - Turntable object broadcast
|
||||
I, Reserved for future use - Turntable object command and control
|
||||
j, Throttle responses
|
||||
J, Throttle queries
|
||||
k, Reserved for future use - Potentially Railcom
|
||||
K, Reserved for future use - Potentially Railcom
|
||||
l, Loco speedbyte/function map broadcast
|
||||
L,
|
||||
m,
|
||||
M, Write DCC packet
|
||||
n,
|
||||
N,
|
||||
o,
|
||||
O, Output broadcast
|
||||
p, Broadcast power state
|
||||
P, Write DCC packet
|
||||
q, Sensor deactivated
|
||||
Q, Sensor activated
|
||||
r, Broadcast address read on programming track
|
||||
R, Read CVs
|
||||
s, Display status
|
||||
S, Sensor configuration
|
||||
t, Cab/loco update command
|
||||
T, Turnout configuration/control
|
||||
u, Reserved for user commands
|
||||
U, Reserved for user commands
|
||||
v,
|
||||
V, Verify CVs
|
||||
w, Write CV on main
|
||||
W, Write CV
|
||||
x,
|
||||
X, Invalid command
|
||||
y,
|
||||
Y, Output broadcast
|
||||
z,
|
||||
Z, Output configuration/control
|
||||
*/
|
||||
|
||||
#include "StringFormatter.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include "DCC.h"
|
||||
@@ -48,7 +121,7 @@
|
||||
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
|
||||
int16_t value=GETHIGHFLASHW(flashList,i); \
|
||||
if (value==INT16_MAX) break; \
|
||||
if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
|
||||
|
||||
@@ -219,6 +292,9 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
|
||||
|
||||
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
{
|
||||
#ifdef DISABLE_PROG
|
||||
(void)ringStream;
|
||||
#endif
|
||||
#ifndef DISABLE_EEPROM
|
||||
(void)EEPROM; // tell compiler not to warn this is unused
|
||||
#endif
|
||||
@@ -467,7 +543,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
@@ -487,9 +563,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
}
|
||||
TrackManager::setJoin(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -500,7 +576,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
@@ -516,12 +592,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
TrackManager::setJoin(false);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
TrackManager::setJoin(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -653,11 +729,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
}
|
||||
else {
|
||||
const FSH * functionNames= RMFT2::getRosterFunctions(id);
|
||||
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id),
|
||||
functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames);
|
||||
}
|
||||
auto rosterName= RMFT2::getRosterName(id);
|
||||
if (!rosterName) rosterName=F("");
|
||||
|
||||
auto functionNames= RMFT2::getRosterFunctions(id);
|
||||
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
|
||||
if (!functionNames) functionNames=F("");
|
||||
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, rosterName, functionNames);
|
||||
}
|
||||
#endif
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
@@ -909,7 +989,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
||||
break;
|
||||
return true;
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
|
@@ -194,8 +194,10 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun
|
||||
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
|
||||
setEOT(data + bitcounter++); // EOT marker
|
||||
dataLen = bitcounter;
|
||||
noInterrupts(); // keep dataReady and dataRepeat consistnet to each other
|
||||
dataReady = true;
|
||||
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
|
||||
interrupts();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -212,6 +214,8 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() {
|
||||
if (dataReady) { // if we have new data, fill while preamble is running
|
||||
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
|
||||
dataReady = false;
|
||||
if (dataRepeat == 0) // all data should go out at least once
|
||||
DIAG(F("Channel %d DCC signal lost data"), channel);
|
||||
}
|
||||
if (dataRepeat > 0) // if a repeat count was specified, work on that
|
||||
dataRepeat--;
|
||||
|
@@ -180,8 +180,8 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
return;
|
||||
}
|
||||
pin_to_channel[pin] = --cnt_channel;
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
ledcSetup(cnt_channel, 1000, 8);
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
} else {
|
||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@
|
||||
#endif
|
||||
#include "DIAG.h"
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
|
||||
// Nucleo-64 boards don't have additional serial ports defined by default
|
||||
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
@@ -52,7 +52,7 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
|
||||
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
|
||||
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
|
||||
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
|
||||
|
129
DCCWaveform.cpp
129
DCCWaveform.cpp
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* @ 2024 Arkadiusz Hahn
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
@@ -35,6 +36,8 @@
|
||||
DCCWaveform DCCWaveform::mainTrack(PREAMBLE_BITS_MAIN, true);
|
||||
DCCWaveform DCCWaveform::progTrack(PREAMBLE_BITS_PROG, false);
|
||||
|
||||
bool DCCWaveform::supportsRailcom=false;
|
||||
bool DCCWaveform::useRailcom=false;
|
||||
|
||||
// This bitmask has 9 entries as each byte is trasmitted as a zero + 8 bits.
|
||||
const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||
@@ -62,6 +65,20 @@ const bool signalTransform[]={
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
void DCCWaveform::begin() {
|
||||
// supportsRailcom depends on hardware capability
|
||||
supportsRailcom = TrackManager::isRailcomCapable();
|
||||
// useRailcom is user switchable at run time.
|
||||
useRailcom=supportsRailcom;
|
||||
|
||||
if (useRailcom) {
|
||||
DIAG(F("Railcom is enabled"));
|
||||
} else {
|
||||
DIAG(F("Railcom is disabled"));
|
||||
}
|
||||
|
||||
TrackManager::setCutout(false,false);
|
||||
TrackManager::setPROGCutout(false,false);
|
||||
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
@@ -69,13 +86,28 @@ void DCCWaveform::loop() {
|
||||
// empty placemarker in case ESP32 needs something here
|
||||
}
|
||||
|
||||
|
||||
bool DCCWaveform::setUseRailcom(bool on) {
|
||||
if (!supportsRailcom) return false;
|
||||
useRailcom=on;
|
||||
if (!on) {
|
||||
// turn off any existing cutout
|
||||
TrackManager::setCutout(false);
|
||||
TrackManager::setPROGCutout(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::interruptHandler() {
|
||||
|
||||
|
||||
// call the timer edge sensitive actions for progtrack and maintrack
|
||||
// member functions would be cleaner but have more overhead
|
||||
byte sigMain=signalTransform[mainTrack.state];
|
||||
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
byte sigMain= signalTransform[mainTrack.state];
|
||||
byte sigProg=TrackManager::progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||
|
||||
// Set the signal state for both tracks
|
||||
TrackManager::setDCCSignal(sigMain);
|
||||
@@ -84,15 +116,26 @@ void DCCWaveform::interruptHandler() {
|
||||
// Refresh the values in the ADCee object buffering the values of the ADC HW
|
||||
ADCee::scan();
|
||||
|
||||
// WAVE_START is at start of bit where we need to find
|
||||
// out if this is an railcom start or stop time
|
||||
if (useRailcom) {
|
||||
if (mainTrack.state==WAVE_MID_1) mainTrack.railcom2(true);
|
||||
if (mainTrack.state==WAVE_START) mainTrack.railcom2(false);
|
||||
if (progTrack.state==WAVE_MID_1) progTrack.railcom2(true);
|
||||
if (progTrack.state==WAVE_START) progTrack.railcom2(false);
|
||||
}
|
||||
|
||||
// Move on in the state engine
|
||||
mainTrack.state=stateTransform[mainTrack.state];
|
||||
progTrack.state=stateTransform[progTrack.state];
|
||||
|
||||
|
||||
// WAVE_PENDING means we dont yet know what the next bit is
|
||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
||||
else DCCACK::checkAck(progTrack.getResets());
|
||||
|
||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
||||
if (progTrack.state==WAVE_PENDING) {
|
||||
progTrack.interrupt2();
|
||||
} else {
|
||||
DCCACK::checkAck(progTrack.getResets());
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
@@ -110,11 +153,39 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
state = WAVE_START;
|
||||
// The +1 below is to allow the preamble generator to create the stop bit
|
||||
// for the previous packet.
|
||||
requiredPreambles = preambleBits+1;
|
||||
requiredPreambles = preambleBits+1;
|
||||
//requiredPreambles <<=1; // double the number of preamble wave halves
|
||||
|
||||
remainingPreambles=0;
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void DCCWaveform::railcom2(bool starting) {
|
||||
bool cutout;
|
||||
if (starting && remainingPreambles==(requiredPreambles-2)) {
|
||||
cutout=true;
|
||||
} else if (!starting && remainingPreambles==(requiredPreambles-5)) {
|
||||
cutout=false;
|
||||
} else {
|
||||
return; // neither start or end of cutout, do nothing
|
||||
}
|
||||
|
||||
if (isMainTrack) {
|
||||
if (TrackManager::progTrackSyncMain) {// we are main track and synced so we take care of prog track as well
|
||||
TrackManager::setPROGCutout(cutout,true);
|
||||
}
|
||||
TrackManager::setCutout(cutout,true);
|
||||
} else {
|
||||
if (!TrackManager::progTrackSyncMain) {// we are prog track and not synced so we take care of ourselves
|
||||
TrackManager::setPROGCutout(cutout,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
|
||||
|
||||
#pragma GCC push_options
|
||||
@@ -127,19 +198,20 @@ void DCCWaveform::interrupt2() {
|
||||
if (remainingPreambles > 0 ) {
|
||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||
remainingPreambles--;
|
||||
|
||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||
// Allow for checkAck and its called functions using 22 bytes more.
|
||||
DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Wave has gone HIGH but what happens next depends on the bit to be transmitted
|
||||
// beware OF 9-BIT MASK generating a zero to start each byte
|
||||
state=(transmitPacket[bytes_sent] & bitMask[bits_sent])? WAVE_MID_1 : WAVE_HIGH_0;
|
||||
bits_sent++;
|
||||
|
||||
|
||||
// If this is the last bit of a byte, prepare for the next byte
|
||||
|
||||
|
||||
if (bits_sent == 9) { // zero followed by 8 bits of a byte
|
||||
//end of Byte
|
||||
bits_sent = 0;
|
||||
@@ -149,30 +221,30 @@ void DCCWaveform::interrupt2() {
|
||||
// end of transmission buffer... repeat or switch to next message
|
||||
bytes_sent = 0;
|
||||
remainingPreambles = requiredPreambles;
|
||||
|
||||
|
||||
if (transmitRepeats > 0) {
|
||||
transmitRepeats--;
|
||||
transmitRepeats--;
|
||||
}
|
||||
else if (packetPending) {
|
||||
// Copy pending packet to transmit packet
|
||||
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
||||
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
||||
// Copy pending packet to transmit packet
|
||||
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
||||
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
||||
|
||||
transmitLength = pendingLength;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
clearResets();
|
||||
transmitLength = pendingLength;
|
||||
transmitRepeats = pendingRepeats;
|
||||
packetPending = false;
|
||||
clearResets();
|
||||
}
|
||||
else {
|
||||
// Fortunately reset and idle packets are the same length
|
||||
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
transmitLength = sizeof(idlePacket);
|
||||
transmitRepeats = 0;
|
||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||
// Fortunately reset and idle packets are the same length
|
||||
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
transmitLength = sizeof(idlePacket);
|
||||
transmitRepeats = 0;
|
||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
@@ -247,6 +319,9 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||
pendingPacket[byteCount] = checksum;
|
||||
pendingLength = byteCount + 1;
|
||||
pendingRepeats = repeats;
|
||||
// DIAG repeated commands (accesories)
|
||||
// if (pendingRepeats > 0)
|
||||
// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG");
|
||||
// The resets will be zero not only now but as well repeats packets into the future
|
||||
clearResets(repeats+1);
|
||||
{
|
||||
|
@@ -33,9 +33,9 @@
|
||||
|
||||
|
||||
// Number of preamble bits.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||
const int PREAMBLE_BITS_MAIN = 16;
|
||||
const int PREAMBLE_BITS_PROG = 22;
|
||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
||||
|
||||
|
||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||
@@ -52,6 +52,11 @@ class DCCWaveform {
|
||||
static void loop();
|
||||
static DCCWaveform mainTrack;
|
||||
static DCCWaveform progTrack;
|
||||
|
||||
static bool supportsRailcom;
|
||||
static bool useRailcom;
|
||||
static bool setUseRailcom(bool on);
|
||||
|
||||
inline void clearRepeats() { transmitRepeats=0; }
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
inline void clearResets() { sentResetsSincePacket=0; }
|
||||
@@ -87,6 +92,7 @@ class DCCWaveform {
|
||||
#endif
|
||||
static void interruptHandler();
|
||||
void interrupt2();
|
||||
void railcom2(bool starting);
|
||||
|
||||
bool isMainTrack;
|
||||
// Transmission controller
|
||||
|
@@ -259,8 +259,9 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
}
|
||||
|
||||
void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||
// turnout descriptions are in low flash F strings
|
||||
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
|
||||
// turnout descriptions are in low flash F strings
|
||||
const FSH *desc = getTurnoutDescription(t->getId());
|
||||
if (desc) t->setHidden(GETFLASH(desc)==0x01);
|
||||
}
|
||||
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
|
@@ -172,6 +172,8 @@ void RMFT2::printMessage(uint16_t id) {
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef TURNOUT
|
||||
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
|
||||
#undef TURNOUTL
|
||||
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
|
||||
#undef PIN_TURNOUT
|
||||
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
|
||||
#undef SERVO_TURNOUT
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202306182208Z"
|
||||
#define GITHUB_SHA "railcomtests-202401142146Z"
|
||||
|
@@ -50,12 +50,12 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||
// Initialisation of EXTurntable
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
112
IO_PCA9555.h
Normal file
112
IO_PCA9555.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* 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_pca9555_h
|
||||
#define io_pca9555_h
|
||||
|
||||
#include "IO_GPIOBase.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for PCA9555 16-bit I/O expander (NXP & Texas Instruments).
|
||||
*/
|
||||
|
||||
class PCA9555 : public GPIOBase<uint16_t> {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
new PCA9555(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_INPUT_P0;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
void _writePullups() override {
|
||||
// Do nothing, pull-ups are always in place for input ports
|
||||
// This function is here for HAL GPIOBase API compatibilitiy
|
||||
|
||||
}
|
||||
void _writePortModes() override {
|
||||
// Write 0 to REG_CONF_P0 & REG_CONF_P1 for in-use pins that are outputs, 1 for others.
|
||||
// PCA9555 & TCA9555, Interrupt is always enabled for raising and falling edge
|
||||
uint16_t temp = ~(_portMode & _portInUse);
|
||||
I2CManager.write(_I2CAddress, 3, REG_CONF_P0, temp, temp>>8);
|
||||
}
|
||||
void _readGpioPort(bool immediate) override {
|
||||
if (immediate) {
|
||||
uint8_t buffer[2];
|
||||
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_INPUT_P0);
|
||||
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0];
|
||||
/* PCA9555 Int bug fix, from PCA9555 datasheet: "must change command byte to something besides 00h
|
||||
* after a Read operation to the PCA9555 device or before reading from
|
||||
* another device"
|
||||
* Recommended solution, read from REG_OUTPUT_P0, then do nothing with the received data
|
||||
* Issue not seen during testing, uncomment if needed
|
||||
*/
|
||||
//I2CManager.read(_I2CAddress, buffer, 2, 1, REG_OUTPUT_P0);
|
||||
} else {
|
||||
// Queue new request
|
||||
requestBlock.wait(); // Wait for preceding operation to complete
|
||||
// Issue new request to read GPIO register
|
||||
I2CManager.queueRequest(&requestBlock);
|
||||
}
|
||||
}
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0];
|
||||
else
|
||||
_portInputState = 0xffff;
|
||||
}
|
||||
|
||||
void _setupDevice() override {
|
||||
// HAL API calls
|
||||
_writePortModes();
|
||||
_writePullups();
|
||||
_writeGpioPort();
|
||||
}
|
||||
|
||||
uint8_t inputBuffer[2];
|
||||
uint8_t outputBuffer[1];
|
||||
|
||||
|
||||
enum {
|
||||
REG_INPUT_P0 = 0x00,
|
||||
REG_INPUT_P1 = 0x01,
|
||||
REG_OUTPUT_P0 = 0x02,
|
||||
REG_OUTPUT_P1 = 0x03,
|
||||
REG_POL_INV_P0 = 0x04,
|
||||
REG_POL_INV_P1 = 0x05,
|
||||
REG_CONF_P0 = 0x06,
|
||||
REG_CONF_P1 = 0x07,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@@ -134,12 +134,13 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Device specific read function
|
||||
// Return the position sent by the rotary encoder software
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
return _position;
|
||||
}
|
||||
|
||||
// Send the feedback value to the rotary encoder software
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (vpin == _firstVpin + 1) {
|
||||
if (value != 0) value = 0x01;
|
||||
@@ -148,9 +149,12 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Send a position update to the rotary encoder software
|
||||
// To be valid, must be 0 to 255, and different to the current position
|
||||
// If the current position is the same, it was initiated by the rotary encoder
|
||||
void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override {
|
||||
if (vpin == _firstVpin + 2) {
|
||||
if (position >= 0 && position <= 255) {
|
||||
if (position >= 0 && position <= 255 && position != _position) {
|
||||
byte newPosition = position & 0xFF;
|
||||
byte _positionBuffer[2] = {RE_MOVE, newPosition};
|
||||
I2CManager.write(_I2CAddress, _positionBuffer, 2);
|
||||
|
391
MotorDriver.cpp
391
MotorDriver.cpp
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/* @ 2024 Arkadiusz Hahn
|
||||
* © 2022-2023 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
@@ -27,11 +27,12 @@
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
unsigned long MotorDriver::globalOverloadStart = 0;
|
||||
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
volatile portreg_t shadowPORTH;
|
||||
|
||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
|
||||
@@ -52,17 +53,17 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
|
||||
fastSignalPin.shadowinout = NULL;
|
||||
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
|
||||
DIAG(F("Found PORTA pin %d"),signalPin);
|
||||
DIAG(F("Found SignalPin PORTA pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
|
||||
DIAG(F("Found PORTB pin %d"),signalPin);
|
||||
DIAG(F("Found SignalPin PORTB pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
|
||||
DIAG(F("Found PORTC pin %d"),signalPin);
|
||||
DIAG(F("Found SignalPin PORTC pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTC;
|
||||
}
|
||||
@@ -75,24 +76,24 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
|
||||
fastSignalPin2.shadowinout = NULL;
|
||||
if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) {
|
||||
DIAG(F("Found PORTA pin %d"),signalPin2);
|
||||
DIAG(F("Found SignalPin2 PORTA pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastSignalPin2.inout == &PORTB)) {
|
||||
DIAG(F("Found PORTB pin %d"),signalPin2);
|
||||
DIAG(F("Found SignalPin2 PORTB pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastSignalPin2.inout == &PORTC)) {
|
||||
DIAG(F("Found PORTC pin %d"),signalPin2);
|
||||
DIAG(F("Found SignalPin2 PORTC pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTC;
|
||||
}
|
||||
}
|
||||
else dualSignal=false;
|
||||
|
||||
if (brake_pin!=UNUSED_PIN){
|
||||
if (brake_pin!=UNUSED_PIN) {
|
||||
invertBrake=brake_pin < 0;
|
||||
if (invertBrake)
|
||||
brake_pin = 0-brake_pin;
|
||||
@@ -102,6 +103,31 @@ 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);
|
||||
fastBrakePin.shadowinout = NULL;
|
||||
|
||||
//DIAG(F("Found BrakePin %d "), brake_pin);
|
||||
if (HAVE_PORTA(fastBrakePin.inout == &PORTA)) {
|
||||
DIAG(F("Found BrakePin PORTA pin %d"),brakePin);
|
||||
fastBrakePin.shadowinout = fastBrakePin.inout;
|
||||
fastBrakePin.inout = &shadowPORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastBrakePin.inout == &PORTB)) {
|
||||
DIAG(F("Found BrakePin PORTB pin %d"),brakePin);
|
||||
fastBrakePin.shadowinout = fastBrakePin.inout;
|
||||
fastBrakePin.inout = &shadowPORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastBrakePin.inout == &PORTC)) {
|
||||
DIAG(F("Found BrakePin PORTC pin %d"),brakePin);
|
||||
fastBrakePin.shadowinout = fastBrakePin.inout;
|
||||
fastBrakePin.inout = &shadowPORTC;
|
||||
}
|
||||
if (HAVE_PORTH(fastBrakePin.inout == &PORTH)) {
|
||||
DIAG(F("Found BrakePin PORTH pin %d"),brakePin);
|
||||
fastBrakePin.shadowinout = fastBrakePin.inout;
|
||||
fastBrakePin.inout = &shadowPORTH;
|
||||
}
|
||||
|
||||
|
||||
setBrake(true); // start with brake on in case we hace DC stuff going on
|
||||
} else {
|
||||
brakePin=UNUSED_PIN;
|
||||
@@ -135,7 +161,11 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
// float calculations or libraray code.
|
||||
senseFactorInternal=sense_factor * senseScale;
|
||||
tripMilliamps=trip_milliamps;
|
||||
rawCurrentTripValue=mA2raw(trip_milliamps);
|
||||
#ifdef MAX_CURRENT
|
||||
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
|
||||
tripMilliamps = MAX_CURRENT;
|
||||
#endif
|
||||
rawCurrentTripValue=mA2raw(tripMilliamps);
|
||||
|
||||
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
|
||||
// This would mean that the values obtained from the ADC never
|
||||
@@ -159,20 +189,24 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
|
||||
}
|
||||
|
||||
// prepare values for current detection
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
||||
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
}
|
||||
|
||||
bool MotorDriver::isRailcomCapable() {
|
||||
return (!dualSignal) && (brakePin!=UNUSED_PIN);
|
||||
}
|
||||
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::ON;
|
||||
if (powerMode == mode) return;
|
||||
//DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode);
|
||||
lastPowerChange[(int)mode] = micros();
|
||||
if (mode == POWERMODE::OVERLOAD)
|
||||
globalOverloadStart = lastPowerChange[(int)mode];
|
||||
bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT);
|
||||
if (on) {
|
||||
// when switching a track On, we need to check the crrentOffset with the pin OFF
|
||||
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
|
||||
@@ -190,30 +224,13 @@ void MotorDriver::setPower(POWERMODE mode) {
|
||||
powerMode=mode;
|
||||
}
|
||||
|
||||
// setBrake applies brake if on == true. So to get
|
||||
// voltage from the motor bride one needs to do a
|
||||
// setBrake(false).
|
||||
// If the brakePin is negative that means the sense
|
||||
// of the brake pin on the motor bridge is inverted
|
||||
// (HIGH == release brake) and setBrake does
|
||||
// compensate for that.
|
||||
//
|
||||
void MotorDriver::setBrake(bool on, bool interruptContext) {
|
||||
if (brakePin == UNUSED_PIN) return;
|
||||
if (!interruptContext) {noInterrupts();}
|
||||
if (on ^ invertBrake)
|
||||
setHIGH(fastBrakePin);
|
||||
else
|
||||
setLOW(fastBrakePin);
|
||||
if (!interruptContext) {interrupts();}
|
||||
}
|
||||
|
||||
bool MotorDriver::canMeasureCurrent() {
|
||||
return currentPin!=UNUSED_PIN;
|
||||
}
|
||||
/*
|
||||
* Return the current reading as pin reading 0 to 1023. If the fault
|
||||
* pin is activated return a negative current to show active fault pin.
|
||||
* Return the current reading as pin reading 0 to max resolution (1024 or 4096).
|
||||
* If the fault pin is activated return a negative current to show active fault pin.
|
||||
* 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
|
||||
@@ -273,6 +290,7 @@ void MotorDriver::startCurrentFromHW() {
|
||||
#endif //ANALOG_READ_INTERRUPT
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#ifdef VARIABLE_TONES
|
||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
247, 262, 294, 330,
|
||||
349, 392, 440, 494,
|
||||
@@ -281,17 +299,43 @@ uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
330, 284, 262, 247,
|
||||
220, 196, 175, 165 };
|
||||
#endif
|
||||
#endif
|
||||
void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
switch(brakePin) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#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
|
||||
TCCR5B = (TCCR5B & B11111000) | B00000100; // same for timer 5 which is like timer 4
|
||||
case 9:
|
||||
case 10:
|
||||
// Timer2 (is differnet)
|
||||
TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM
|
||||
TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz
|
||||
//DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
|
||||
TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
|
||||
TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// 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;
|
||||
@@ -299,11 +343,13 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
{
|
||||
int f = 131;
|
||||
#ifdef VARIABLE_TONES
|
||||
if (tSpeed > 2) {
|
||||
if (tSpeed <= 58) {
|
||||
f = taurustones[ (tSpeed-2)/2 ] ;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
}
|
||||
#endif
|
||||
@@ -342,7 +388,60 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
interrupts();
|
||||
}
|
||||
}
|
||||
|
||||
void MotorDriver::throttleInrush(bool on) {
|
||||
if (brakePin == UNUSED_PIN)
|
||||
return;
|
||||
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
|
||||
return;
|
||||
byte duty = on ? 208 : 0;
|
||||
if (invertBrake)
|
||||
duty = 255-duty;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
if(on) {
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
||||
} else {
|
||||
ledcDetachPin(brakePin);
|
||||
}
|
||||
#else
|
||||
if(on){
|
||||
switch(brakePin) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
case 9:
|
||||
case 10:
|
||||
// Timer2 (is different)
|
||||
TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM
|
||||
TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max)
|
||||
DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||
break;
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
// Timer4
|
||||
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
|
||||
TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
|
||||
break;
|
||||
case 46:
|
||||
case 45:
|
||||
case 44:
|
||||
// Timer5
|
||||
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
|
||||
TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
analogWrite(brakePin,duty);
|
||||
#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;
|
||||
@@ -368,67 +467,173 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
||||
result.inout = portOutputRegister(port);
|
||||
result.maskHIGH = digitalPinToBitMask(pin);
|
||||
result.maskLOW = ~result.maskHIGH;
|
||||
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
||||
//DIAG(F("MotorDriver::getFastPin port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
// checkPowerOverload(useProgLimit, trackno)
|
||||
// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
|
||||
// byte trackno: trackmanager knows it's number (could be skipped?)
|
||||
//
|
||||
// Short ciruit handling strategy:
|
||||
//
|
||||
// There are the following power states: ON ALERT OVERLOAD OFF
|
||||
// OFF state is only changed to/from manually. Power is on
|
||||
// during ON and ALERT. Power is off during OVERLOAD and OFF.
|
||||
// The overload mechanism changes between the other states like
|
||||
//
|
||||
// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON
|
||||
// or
|
||||
// ON -1-> ALERT -4-> ON
|
||||
//
|
||||
// Times are in class MotorDriver (MotorDriver.h).
|
||||
//
|
||||
// 1. ON to ALERT:
|
||||
// Transition on fault pin condition or current overload
|
||||
//
|
||||
// 2. ALERT to OVERLOAD:
|
||||
// Transition happens if different timeouts have elapsed.
|
||||
// If only the fault pin is active, timeout is
|
||||
// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms)
|
||||
// If only overcurrent is detected, timeout is
|
||||
// POWER_SAMPLE_IGNORE_CURRENT (100ms)
|
||||
// If fault pin and overcurrent are active, timeout is
|
||||
// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms)
|
||||
// Transition to OVERLOAD turns off power to the affected
|
||||
// output (unless fault pins are shared)
|
||||
// If the transition conditions are not fullfilled,
|
||||
// transition according to 4 is tested.
|
||||
//
|
||||
// 3. OVERLOAD to ALERT
|
||||
// Transiton happens when timeout has elapsed, timeout
|
||||
// is named power_sample_overload_wait. It is started
|
||||
// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry
|
||||
// to OVERLOAD and then increased by a factor of 2
|
||||
// at further entries to the OVERLOAD condition. This
|
||||
// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached.
|
||||
// power_sample_overload_wait is reset by a poweroff or
|
||||
// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON.
|
||||
// After timeout power is turned on again and state
|
||||
// goes back to ALERT.
|
||||
//
|
||||
// 4. ALERT to ON
|
||||
// Transition happens by watching the current and fault pin
|
||||
// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If
|
||||
// values have been good during that time, transition is
|
||||
// made back to ON. Note that even if state is back to ON,
|
||||
// the power_sample_overload_wait time is first reset
|
||||
// later (see above).
|
||||
//
|
||||
// The time keeping is handled by timestamps lastPowerChange[]
|
||||
// which are set by each power change and by lastBadSample which
|
||||
// keeps track if conditions during ALERT have been good enough
|
||||
// to go back to ON. The time differences are calculated by
|
||||
// microsSinceLastPowerChange().
|
||||
//
|
||||
|
||||
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
|
||||
|
||||
// Trackname for diag messages later
|
||||
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (commonFaultPin) {
|
||||
if (lastCurrent < tripValue) {
|
||||
setPower(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A');
|
||||
} else {
|
||||
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
|
||||
case POWERMODE::OFF: {
|
||||
lastPowerMode = POWERMODE::OFF;
|
||||
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
break;
|
||||
}
|
||||
|
||||
case POWERMODE::ON: {
|
||||
lastPowerMode = POWERMODE::ON;
|
||||
bool cF = checkFault();
|
||||
bool cC = checkCurrent(useProgLimit);
|
||||
if(cF || cC ) {
|
||||
if (cC) {
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A',
|
||||
cF ? "FAULT" : "",
|
||||
mA);
|
||||
} else {
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
||||
}
|
||||
setPower(POWERMODE::ALERT);
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
}
|
||||
// all well
|
||||
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
|
||||
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case POWERMODE::ALERT: {
|
||||
// set local flags that handle how much is output to diag (do not output duplicates)
|
||||
bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
|
||||
bool powerModeChange = (powerMode != lastPowerMode);
|
||||
unsigned long now = micros();
|
||||
if (powerModeChange)
|
||||
lastBadSample = now;
|
||||
lastPowerMode = POWERMODE::ALERT;
|
||||
// check how long we have been in this state
|
||||
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
|
||||
if(checkFault()) {
|
||||
throttleInrush(true);
|
||||
lastBadSample = now;
|
||||
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
|
||||
if ( mslpc < timeout) {
|
||||
if (powerModeChange)
|
||||
DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout);
|
||||
break;
|
||||
}
|
||||
DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait);
|
||||
throttleInrush(false);
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
break;
|
||||
}
|
||||
if (checkCurrent(useProgLimit)) {
|
||||
lastBadSample = now;
|
||||
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
|
||||
if (powerModeChange) {
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA);
|
||||
}
|
||||
break;
|
||||
}
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
|
||||
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
|
||||
throttleInrush(false);
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
break;
|
||||
}
|
||||
// all well
|
||||
unsigned long goodtime = micros() - lastBadSample;
|
||||
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
|
||||
if (true || notFromOverload) { // we did a RESTORE message XXX
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
|
||||
}
|
||||
throttleInrush(false);
|
||||
setPower(POWERMODE::ON);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case POWERMODE::OVERLOAD: {
|
||||
lastPowerMode = POWERMODE::OVERLOAD;
|
||||
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
|
||||
if (mslpc > power_sample_overload_wait) {
|
||||
// adjust next wait time
|
||||
power_sample_overload_wait *= 2;
|
||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||
// power on test
|
||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||
setPower(POWERMODE::ALERT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
142
MotorDriver.h
142
MotorDriver.h
@@ -27,6 +27,10 @@
|
||||
#include "IODevice.h"
|
||||
#include "DCCTimer.h"
|
||||
|
||||
// use powers of two so we can do logical and/or on the track modes in if clauses.
|
||||
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||
@@ -39,6 +43,7 @@
|
||||
#define HAVE_PORTA(X) X
|
||||
#define HAVE_PORTB(X) X
|
||||
#define HAVE_PORTC(X) X
|
||||
#define HAVE_PORTH(X) X
|
||||
#endif
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define HAVE_PORTB(X) X
|
||||
@@ -70,7 +75,9 @@
|
||||
#ifndef HAVE_PORTC
|
||||
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
|
||||
#ifndef HAVE_PORTH
|
||||
#define HAVE_PORTH(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
|
||||
@@ -106,45 +113,71 @@ struct FASTPIN {
|
||||
extern volatile portreg_t shadowPORTA;
|
||||
extern volatile portreg_t shadowPORTB;
|
||||
extern volatile portreg_t shadowPORTC;
|
||||
extern volatile portreg_t shadowPORTH;
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
|
||||
|
||||
class MotorDriver {
|
||||
public:
|
||||
|
||||
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, int16_t fault_pin);
|
||||
|
||||
void setPower( POWERMODE mode);
|
||||
|
||||
POWERMODE getPower() { return powerMode;}
|
||||
|
||||
// as the port registers can be shadowed to get syncronized DCC signals
|
||||
// we need to take care of that and we have to turn off interrupts if
|
||||
// we setSignal() or setBrake() or setPower() during that time as
|
||||
// otherwise the call from interrupt context can undo whatever we do
|
||||
// from outside interrupt
|
||||
void setBrake( bool on, bool interruptContext=false);
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
|
||||
|
||||
// setBrake applies brake if on == true. So to get
|
||||
// voltage from the motor bride one needs to do a
|
||||
// setBrake(false).
|
||||
// If the brakePin is negative that means the sense
|
||||
// of the brake pin on the motor bridge is inverted
|
||||
// (HIGH == release brake) and setBrake does
|
||||
// compensate for that.
|
||||
__attribute__((always_inline)) inline void setBrake(bool on, bool interruptContext=false) {
|
||||
if (brakePin == UNUSED_PIN) return;
|
||||
if (!interruptContext) {noInterrupts();}
|
||||
if (on ^ invertBrake) {
|
||||
setHIGH(fastBrakePin);
|
||||
} else {
|
||||
setLOW(fastBrakePin);
|
||||
}
|
||||
if (!interruptContext) {interrupts();}
|
||||
};
|
||||
|
||||
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
if (trackPWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
}
|
||||
else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
} else {
|
||||
if (high) {
|
||||
setHIGH(fastSignalPin);
|
||||
if (dualSignal) setLOW(fastSignalPin2);
|
||||
} else {
|
||||
setLOW(fastSignalPin);
|
||||
if (dualSignal) setHIGH(fastSignalPin2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline void enableSignal(bool on) {
|
||||
if (on)
|
||||
pinMode(signalPin, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin, INPUT);
|
||||
if (on) {
|
||||
pinMode(signalPin, OUTPUT);
|
||||
} else {
|
||||
pinMode(signalPin, INPUT);
|
||||
}
|
||||
};
|
||||
|
||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||
void setDCSignal(byte speedByte);
|
||||
void throttleInrush(bool on);
|
||||
inline void detachDCSignal() {
|
||||
#if defined(__arm__)
|
||||
pinMode(brakePin, OUTPUT);
|
||||
@@ -173,9 +206,13 @@ class MotorDriver {
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
bool isPWMCapable();
|
||||
bool isRailcomCapable();
|
||||
bool canMeasureCurrent();
|
||||
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte setCommonFaultPin() {
|
||||
return commonFaultPin = true;
|
||||
}
|
||||
inline byte getFaultPin() {
|
||||
return faultPin;
|
||||
}
|
||||
@@ -186,17 +223,46 @@ class MotorDriver {
|
||||
inline void setTrackLetter(char c) {
|
||||
trackLetter = c;
|
||||
};
|
||||
// this returns how much time has passed since the last power change. If it
|
||||
// was really long ago (approx > 52min) advance counter approx 35 min so that
|
||||
// we are at 18 minutes again. Times for 32 bit unsigned long.
|
||||
inline unsigned long microsSinceLastPowerChange(POWERMODE mode) {
|
||||
unsigned long now = micros();
|
||||
unsigned long diff = now - lastPowerChange[(int)mode];
|
||||
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
|
||||
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
|
||||
return diff;
|
||||
};
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
bool sampleCurrentFromHW();
|
||||
void startCurrentFromHW();
|
||||
#endif
|
||||
inline void setMode(TRACK_MODE m) {
|
||||
trackMode = m;
|
||||
};
|
||||
inline TRACK_MODE getMode() {
|
||||
return trackMode;
|
||||
};
|
||||
private:
|
||||
char trackLetter = '?';
|
||||
bool isProgTrack = false; // tells us if this is a prog track
|
||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
getFastPin(type, pin, 0, result);
|
||||
}
|
||||
inline void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
getFastPin(type, pin, 0, result);
|
||||
};
|
||||
// side effect sets lastCurrent and tripValue
|
||||
inline bool checkCurrent(bool useProgLimit) {
|
||||
tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
|
||||
lastCurrent = getCurrentRaw();
|
||||
if (lastCurrent < 0)
|
||||
lastCurrent = -lastCurrent;
|
||||
return lastCurrent >= tripValue;
|
||||
};
|
||||
// side effect sets lastCurrent
|
||||
inline bool checkFault() {
|
||||
lastCurrent = getCurrentRaw();
|
||||
return lastCurrent < 0;
|
||||
};
|
||||
VPIN powerPin;
|
||||
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
|
||||
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
|
||||
@@ -217,10 +283,14 @@ class MotorDriver {
|
||||
int rawCurrentTripValue;
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
POWERMODE lastPowerMode;
|
||||
unsigned long lastPowerChange[4]; // timestamp in microseconds
|
||||
unsigned long lastBadSample; // timestamp in microseconds
|
||||
// used to sync restore time when common Fault pin detected
|
||||
static unsigned long globalOverloadStart; // timestamp in microseconds
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
int lastCurrent; //temp value
|
||||
int tripValue; //temp value
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
volatile unsigned long sampleCurrentTimestamp;
|
||||
volatile uint16_t sampleCurrent;
|
||||
@@ -228,16 +298,28 @@ class MotorDriver {
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
static const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
static const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
// Times for overload management. Unit: microseconds.
|
||||
// Base for wait time until power is turned on again
|
||||
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL;
|
||||
// Time after we consider all faults old and forgotten
|
||||
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
|
||||
// Time after which we consider a ALERT over
|
||||
static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL;
|
||||
// How long to ignore fault pin if current is under limit
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 100000UL;
|
||||
// How long to ignore fault pin if current is higher than limit
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL;
|
||||
// How long to wait between overcurrent and turning off
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL;
|
||||
// Upper limit for retry period
|
||||
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
|
||||
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
static const int TRIP_CURRENT_PROG=250;
|
||||
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
unsigned int power_good_counter = 0;
|
||||
TRACK_MODE trackMode = TRACK_MODE_NONE; // we assume track not assigned at startup
|
||||
|
||||
};
|
||||
#endif
|
||||
|
@@ -117,6 +117,24 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
|
||||
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
|
||||
case 'M':
|
||||
{ // this prints a unsigned long microseconds time in readable format
|
||||
unsigned long time = va_arg(args, long);
|
||||
if (time >= 2000) {
|
||||
time = time / 1000;
|
||||
if (time >= 2000) {
|
||||
printPadded(stream, time/1000, formatWidth, formatLeft);
|
||||
stream->print(F("sec"));
|
||||
} else {
|
||||
printPadded(stream,time, formatWidth, formatLeft);
|
||||
stream->print(F("msec"));
|
||||
}
|
||||
} else {
|
||||
printPadded(stream,time, formatWidth, formatLeft);
|
||||
stream->print(F("usec"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
//case 'f': stream->print(va_arg(args, double), 2); break;
|
||||
//format width prefix
|
||||
case '-':
|
||||
|
141
TrackManager.cpp
141
TrackManager.cpp
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/* @ 2024 Arkadiusz Hahn
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
@@ -31,20 +31,20 @@
|
||||
|
||||
#define APPLY_BY_MODE(findmode,function) \
|
||||
FOR_EACH_TRACK(t) \
|
||||
if (trackMode[t]==findmode) \
|
||||
if (track[t]->getMode()==findmode) \
|
||||
track[t]->function;
|
||||
#ifndef DISABLE_PROG
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
#endif
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_OFF = 22479;
|
||||
const int16_t HASH_KEYWORD_NONE = -26550;
|
||||
const int16_t HASH_KEYWORD_DC = 2183;
|
||||
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
|
||||
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
|
||||
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
|
||||
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS];
|
||||
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
|
||||
|
||||
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
|
||||
@@ -53,7 +53,7 @@ bool TrackManager::progTrackSyncMain=false;
|
||||
bool TrackManager::progTrackBoosted=false;
|
||||
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
byte TrackManager::tempProgTrack=MAX_TRACKS+1;
|
||||
byte TrackManager::tempProgTrack=MAX_TRACKS+1; // MAX_TRACKS+1 is the unused flag
|
||||
#endif
|
||||
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
@@ -74,7 +74,7 @@ void TrackManager::sampleCurrent() {
|
||||
waiting = false;
|
||||
tr++;
|
||||
if (tr > lastTrack) tr = 0;
|
||||
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
|
||||
if (lastTrack < 2 || track[tr]->getMode() & 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.
|
||||
@@ -85,7 +85,7 @@ void TrackManager::sampleCurrent() {
|
||||
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 )) {
|
||||
if (track[tr]->getMode() & ( 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;
|
||||
@@ -123,19 +123,27 @@ void TrackManager::Setup(const FSH * shieldname,
|
||||
setTrackMode(1,TRACK_MODE_MAIN);
|
||||
#endif
|
||||
|
||||
// TODO Fault pin config for odd motor boards (example pololu)
|
||||
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
// && (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
DCC::begin(shieldname);
|
||||
// Fault pin config for odd motor boards (example pololu)
|
||||
FOR_EACH_TRACK(t) {
|
||||
for (byte s=t+1;s<=lastTrack;s++) {
|
||||
if (track[t]->getFaultPin() != UNUSED_PIN &&
|
||||
track[t]->getFaultPin() == track[s]->getFaultPin()) {
|
||||
track[t]->setCommonFaultPin();
|
||||
track[s]->setCommonFaultPin();
|
||||
DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A');
|
||||
}
|
||||
}
|
||||
}
|
||||
DCC::setShieldName(shieldname);
|
||||
}
|
||||
|
||||
void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]=driver;
|
||||
if (driver) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
track[t]->setTrackLetter('A'+t);
|
||||
lastTrack=t;
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
track[t]->setMode(TRACK_MODE_NONE);
|
||||
track[t]->setTrackLetter('A'+t);
|
||||
lastTrack=t;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,10 +159,41 @@ void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
(void) on;
|
||||
// TODO Cutout needs fake ports as well
|
||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
||||
// setCutout() for MAIN track
|
||||
void TrackManager::setCutout( bool on,bool interruptContext) {
|
||||
//(void) on; // avoid compiler warning -Wunused
|
||||
// Cutout needs fake ports as well
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
HAVE_PORTH(shadowPORTH=PORTH);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setBrake(on,interruptContext));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
HAVE_PORTH(PORTH=shadowPORTH);
|
||||
}
|
||||
|
||||
void TrackManager::setPROGCutout( bool on,bool interruptContext) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
HAVE_PORTH(shadowPORTH=PORTH);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setBrake(on,interruptContext));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
HAVE_PORTH(PORTH=shadowPORTH);
|
||||
}
|
||||
|
||||
// true when there is any railcom capable MAIN track
|
||||
bool TrackManager::isRailcomCapable() {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if((track[t]->getMode()==TRACK_MODE_MAIN) && (track[t]->isRailcomCapable())){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// setPROGSignal(), called from interrupt context
|
||||
@@ -174,22 +213,27 @@ void TrackManager::setPROGSignal( bool on) {
|
||||
// with interrupts turned off around the critical section
|
||||
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackDCAddr[t]!=cab) continue;
|
||||
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
if (trackDCAddr[t]!=cab && cab != 0) continue;
|
||||
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
}
|
||||
}
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
//DIAG(F("Track=%c"),trackToSet+'A');
|
||||
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
|
||||
&& !track[trackToSet]->brakeCanPWM()) {
|
||||
DIAG(F("Brake pin can't PWM: No DC"));
|
||||
return false;
|
||||
}
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
DIAG(F("Uno has no PWM timers available for DC"));
|
||||
return false;
|
||||
#endif
|
||||
if (!track[trackToSet]->brakeCanPWM()) {
|
||||
DIAG(F("Brake pin can't PWM: No DC"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// remove pin from MUX matrix and turn it off
|
||||
@@ -210,9 +254,9 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
#endif
|
||||
// only allow 1 track to be prog
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]->setMode(TRACK_MODE_NONE);
|
||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||
streamTrackState(NULL,t);
|
||||
}
|
||||
@@ -220,7 +264,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
} else {
|
||||
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
|
||||
}
|
||||
trackMode[trackToSet]=mode;
|
||||
track[trackToSet]->setMode(mode);
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
streamTrackState(NULL,trackToSet);
|
||||
|
||||
@@ -247,7 +291,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
// DC tracks must not have the DCC PWM switched on
|
||||
// so we globally turn it off if one of the PWM
|
||||
// capable tracks is now DC or DCX.
|
||||
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
|
||||
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
|
||||
if (track[t]->isPWMCapable()) {
|
||||
canDo=false; // this track is capable but can not run PWM
|
||||
break; // in this mode, so abort and prevent globally below
|
||||
@@ -255,7 +299,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
track[t]->trackPWM=false; // this track sure can not run with PWM
|
||||
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
||||
}
|
||||
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
|
||||
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
|
||||
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
||||
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
||||
canDo &= track[t]->trackPWM;
|
||||
@@ -293,7 +337,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
if (trackMode[t]==TRACK_MODE_DCX)
|
||||
if (track[t]->getMode()==TRACK_MODE_DCX)
|
||||
speedByte = speedByte ^ 128; // reverse direction bit
|
||||
track[t]->setDCSignal(speedByte);
|
||||
}
|
||||
@@ -320,8 +364,8 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
#endif
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
|
||||
return setTrackMode(p[0],TRACK_MODE_OFF);
|
||||
if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE>
|
||||
return setTrackMode(p[0],TRACK_MODE_NONE);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
@@ -339,7 +383,7 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||
// null stream means send to commandDistributor for broadcast
|
||||
if (track[t]==NULL) return;
|
||||
auto format=F("");
|
||||
switch(trackMode[t]) {
|
||||
switch(track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
format=F("<= %c MAIN>\n");
|
||||
break;
|
||||
@@ -348,8 +392,8 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||
format=F("<= %c PROG>\n");
|
||||
break;
|
||||
#endif
|
||||
case TRACK_MODE_OFF:
|
||||
format=F("<= %c OFF>\n");
|
||||
case TRACK_MODE_NONE:
|
||||
format=F("<= %c NONE>\n");
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
format=F("<= %c EXT>\n");
|
||||
@@ -379,13 +423,13 @@ void TrackManager::loop() {
|
||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||
if (track[nextCycleTrack]==NULL) return;
|
||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
|
||||
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
|
||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||
}
|
||||
|
||||
MotorDriver * TrackManager::getProgDriver() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -393,7 +437,7 @@ MotorDriver * TrackManager::getProgDriver() {
|
||||
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
std::vector<MotorDriver *> v;
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
return v;
|
||||
}
|
||||
#endif
|
||||
@@ -403,7 +447,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (trackMode[t]) {
|
||||
switch (track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
@@ -431,7 +475,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
case TRACK_MODE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -439,8 +483,8 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
@@ -484,7 +528,7 @@ void TrackManager::setJoin(bool joined) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (joined) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackMode[t]==TRACK_MODE_PROG) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) {
|
||||
tempProgTrack = t;
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
break;
|
||||
@@ -492,7 +536,12 @@ void TrackManager::setJoin(bool joined) {
|
||||
}
|
||||
} else {
|
||||
if (tempProgTrack != MAX_TRACKS+1) {
|
||||
// as setTrackMode with TRACK_MODE_PROG defaults to
|
||||
// power off, we will take the current power state
|
||||
// of our track and then preserve that state.
|
||||
POWERMODE tPTmode = track[tempProgTrack]->getPower(); //get current power status of this track
|
||||
setTrackMode(tempProgTrack, TRACK_MODE_PROG);
|
||||
track[tempProgTrack]->setPower(tPTmode); //set track status as it was before
|
||||
tempProgTrack = MAX_TRACKS+1;
|
||||
}
|
||||
}
|
||||
|
@@ -27,10 +27,6 @@
|
||||
#include "MotorDriver.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
|
||||
// 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;
|
||||
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
|
||||
@@ -55,9 +51,11 @@ class TrackManager {
|
||||
);
|
||||
|
||||
static void setDCCSignal( bool on);
|
||||
static void setCutout( bool on);
|
||||
static void setPROGSignal( bool on);
|
||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||
static void setCutout( bool on,bool interruptContext=false);
|
||||
static void setPROGCutout( bool on,bool interruptContext=false);
|
||||
static bool isRailcomCapable();
|
||||
static MotorDriver * getProgDriver();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
@@ -100,7 +98,6 @@ class TrackManager {
|
||||
static POWERMODE mainPowerGuess;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
static TRACK_MODE trackMode[MAX_TRACKS];
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static byte tempProgTrack; // holds the prog track number during join
|
||||
|
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
© 2021, Harald Barth.
|
||||
© 2023 Paul M. Antoine
|
||||
© 2021 Harald Barth
|
||||
© 2023 Nathan Kellenicki
|
||||
|
||||
This file is part of CommandStation-EX
|
||||
|
||||
@@ -20,6 +22,7 @@
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <vector>
|
||||
#include "defines.h"
|
||||
#include "ESPmDNS.h"
|
||||
#include <WiFi.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "WifiESP32.h"
|
||||
@@ -105,11 +108,18 @@ void wifiLoop(void *){
|
||||
}
|
||||
#endif
|
||||
|
||||
char asciitolower(char in) {
|
||||
if (in <= 'Z' && in >= 'A')
|
||||
return in - ('Z' - 'z');
|
||||
return in;
|
||||
}
|
||||
|
||||
bool WifiESP::setup(const char *SSid,
|
||||
const char *password,
|
||||
const char *hostname,
|
||||
int port,
|
||||
const byte channel) {
|
||||
const byte channel,
|
||||
const bool forceAP) {
|
||||
bool havePassword = true;
|
||||
bool haveSSID = true;
|
||||
bool wifiUp = false;
|
||||
@@ -137,7 +147,8 @@ bool WifiESP::setup(const char *SSid,
|
||||
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
||||
havePassword = false;
|
||||
|
||||
if (haveSSID && havePassword) {
|
||||
if (haveSSID && havePassword && !forceAP) {
|
||||
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
|
||||
WiFi.mode(WIFI_STA);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
@@ -174,16 +185,20 @@ bool WifiESP::setup(const char *SSid,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!haveSSID) {
|
||||
if (!haveSSID || forceAP) {
|
||||
// prepare all strings
|
||||
String strSSID("DCC_");
|
||||
String strPass("PASS_");
|
||||
String strMac = WiFi.macAddress();
|
||||
strMac.remove(0,9);
|
||||
strMac.replace(":","");
|
||||
strMac.replace(":","");
|
||||
strSSID.concat(strMac);
|
||||
strPass.concat(strMac);
|
||||
String strSSID(forceAP ? SSid : "DCCEX_");
|
||||
String strPass(forceAP ? password : "PASS_");
|
||||
if (!forceAP) {
|
||||
String strMac = WiFi.macAddress();
|
||||
strMac.remove(0,9);
|
||||
strMac.replace(":","");
|
||||
strMac.replace(":","");
|
||||
// convert mac addr hex chars to lower case to be compatible with AT software
|
||||
std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower);
|
||||
strSSID.concat(strMac);
|
||||
strPass.concat(strMac);
|
||||
}
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
@@ -209,6 +224,15 @@ bool WifiESP::setup(const char *SSid,
|
||||
// no idea to go on
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now Wifi is up, register the mDNS service
|
||||
if(!MDNS.begin(hostname)) {
|
||||
DIAG(F("Wifi setup failed to start mDNS"));
|
||||
}
|
||||
if(!MDNS.addService("withrottle", "tcp", 2560)) {
|
||||
DIAG(F("Wifi setup failed to add withrottle service to mDNS"));
|
||||
}
|
||||
|
||||
server = new WiFiServer(port); // start listening on tcp port
|
||||
server->begin();
|
||||
// server started here
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* © 2021, Harald Barth.
|
||||
* © 2021 Harald Barth
|
||||
* © 2023 Nathan Kellenicki
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -31,7 +32,8 @@ public:
|
||||
const char *wifiPassword,
|
||||
const char *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
const byte channel,
|
||||
const bool forceAP);
|
||||
static void loop();
|
||||
private:
|
||||
};
|
||||
|
@@ -2,6 +2,7 @@
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -59,7 +60,7 @@ Stream * WifiInterface::wifiStream;
|
||||
#if defined(ARDUINO_ARCH_STM32)
|
||||
// Handle serial ports availability on STM32 for variants!
|
||||
// #undef NUM_SERIAL
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial1
|
||||
#define SERIAL3 Serial6
|
||||
@@ -67,9 +68,11 @@ Stream * WifiInterface::wifiStream;
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial3
|
||||
#define SERIAL3 Serial5
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG)
|
||||
#define NUM_SERIAL 2
|
||||
#define SERIAL1 Serial6
|
||||
#else
|
||||
#warning This variant of Nucleo not yet explicitly supported
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -83,7 +86,8 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel) {
|
||||
const byte channel,
|
||||
const bool forceAP) {
|
||||
|
||||
wifiSerialState wifiUp = WIFI_NOAT;
|
||||
|
||||
@@ -95,12 +99,13 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
(void) hostname;
|
||||
(void) port;
|
||||
(void) channel;
|
||||
(void) forceAP;
|
||||
#endif
|
||||
|
||||
// See if the WiFi is attached to the first serial port
|
||||
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
|
||||
SERIAL1.begin(serial_link_speed);
|
||||
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
#endif
|
||||
|
||||
// Other serials are tried, depending on hardware.
|
||||
@@ -110,7 +115,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial2.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -121,7 +126,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
SERIAL3.begin(serial_link_speed);
|
||||
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -139,7 +144,7 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
}
|
||||
|
||||
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
const FSH* hostname, int port, byte channel, bool forceAP) {
|
||||
wifiSerialState wifiState;
|
||||
static uint8_t ntry = 0;
|
||||
ntry++;
|
||||
@@ -148,7 +153,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
||||
|
||||
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
|
||||
|
||||
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||
wifiState = setup2( SSid, password, hostname, port, channel, forceAP);
|
||||
|
||||
if (wifiState == WIFI_NOAT) {
|
||||
LCD(4, F("WiFi no AT chip"));
|
||||
@@ -172,7 +177,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
#endif
|
||||
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
const FSH* hostname, int port, byte channel) {
|
||||
const FSH* hostname, int port, byte channel, bool forceAP) {
|
||||
bool ipOK = false;
|
||||
bool oldCmd = false;
|
||||
|
||||
@@ -195,7 +200,23 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
// Display the AT version information
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
checkForOK(2000, true, false); // Makes this visible on the console
|
||||
if (checkForOK(2000, F("AT version:"), true, false)) {
|
||||
char version[] = "0.0.0.0-xxx";
|
||||
for (int i=0; i<11;i++) {
|
||||
while(!wifiStream->available());
|
||||
version[i]=wifiStream->read();
|
||||
StringFormatter::printEscape(version[i]);
|
||||
}
|
||||
if ((version[0] == '0') ||
|
||||
(version[0] == '2' && version[2] == '0') ||
|
||||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0'
|
||||
&& version[7] == '-' && version[8] == 'd' && version[9] == 'e' && version[10] == 'v')) {
|
||||
DIAG(F("You need to up/downgrade the ESP firmware"));
|
||||
SSid = F("UPDATE_ESP_FIRMWARE");
|
||||
forceAP = true;
|
||||
}
|
||||
}
|
||||
checkForOK(2000, true, false);
|
||||
|
||||
#ifdef DONT_TOUCH_WIFI_CONF
|
||||
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
|
||||
@@ -225,7 +246,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
}
|
||||
} else {
|
||||
} else if (!forceAP) {
|
||||
// SSID was configured, so we assume station (client) mode.
|
||||
if (oldCmd) {
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
@@ -285,14 +306,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
i=0;
|
||||
do {
|
||||
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// unconfigured
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
|
||||
oldCmd ? "" : "_CUR", macTail, macTail, channel);
|
||||
if (!forceAP) {
|
||||
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// unconfigured
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
|
||||
oldCmd ? "" : "_CUR", macTail, macTail, channel);
|
||||
} else {
|
||||
// password configured by user
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||
macTail, password, channel);
|
||||
}
|
||||
} else {
|
||||
// password configured by user
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||
macTail, password, channel);
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"),
|
||||
oldCmd ? "" : "_CUR", SSid, password, channel);
|
||||
}
|
||||
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
|
||||
if (i >= 2)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020, Harald Barth.
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -36,17 +37,18 @@ public:
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
const byte channel,
|
||||
const bool forceAP);
|
||||
static void loop();
|
||||
static void ATCommand(HardwareSerial * stream,const byte *command);
|
||||
|
||||
private:
|
||||
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
const FSH *hostname, int port, byte channel, bool forceAP);
|
||||
static Stream *wifiStream;
|
||||
static DCCEXParser parser;
|
||||
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
const FSH *hostname, int port, byte channel, bool forceAP);
|
||||
static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true);
|
||||
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
||||
static bool connected;
|
||||
|
@@ -4,6 +4,7 @@
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2023 Nathan Kellenicki
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -57,6 +58,21 @@ The configuration file for DCC-EX Command Station
|
||||
// +-----------------------v
|
||||
//
|
||||
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// If you want to restrict the maximum current LOWER than what your
|
||||
// motor shield can provide, you can do that here. For example if you
|
||||
// have a motor shield that can provide 5A and your power supply can
|
||||
// only provide 2.5A then you should restict the maximum current to
|
||||
// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before
|
||||
// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example
|
||||
// it would be 2250, adjust the number according to your PS. If your
|
||||
// PS has a higher rating than your motor shield you do not need this.
|
||||
// You can use this as well if you are cautious and your trains do not
|
||||
// need full current.
|
||||
// #define MAX_CURRENT 2250
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// The IP port to talk to a WIFI or Ethernet shield.
|
||||
@@ -108,6 +124,11 @@ The configuration file for DCC-EX Command Station
|
||||
// this line exists or not. If you need to use an alternate channel (we recommend
|
||||
// using only 1,6, or 11) you may change it here.
|
||||
#define WIFI_CHANNEL 1
|
||||
//
|
||||
// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this
|
||||
// true. Otherwise it is assumed that you'd like to connect to an existing network
|
||||
// with that SSID.
|
||||
#define WIFI_FORCE_AP false
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
|
11
defines.h
11
defines.h
@@ -182,6 +182,15 @@
|
||||
#define WIFI_ON false
|
||||
#endif
|
||||
|
||||
#ifndef WIFI_FORCE_AP
|
||||
#define WIFI_FORCE_AP false
|
||||
#else
|
||||
#if WIFI_FORCE_AP==true || WIFI_FORCE_AP==false
|
||||
#else
|
||||
#error WIFI_FORCE_AP needs to be true or false
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ENABLE_ETHERNET
|
||||
#if defined(HAS_ENOUGH_MEMORY)
|
||||
#define ETHERNET_ON true
|
||||
@@ -205,7 +214,7 @@
|
||||
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||
|
||||
#if __has_include ( "myAutomation.h")
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
|
||||
#define EXRAIL_ACTIVE
|
||||
#else
|
||||
#define EXRAIL_WARNING
|
||||
|
23
installer.sh
23
installer.sh
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# © 2022 Harald Barth
|
||||
# © 2022,2023 Harald Barth
|
||||
#
|
||||
# This file is part of CommandStation-EX
|
||||
#
|
||||
@@ -29,14 +29,33 @@ ACLI="./bin/arduino-cli"
|
||||
|
||||
function need () {
|
||||
type -p $1 > /dev/null && return
|
||||
dpkg -l $1 2>&1 | egrep ^ii >/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 cat /etc/issue | egrep '^Raspbian' 2>&1 >/dev/null ; then
|
||||
# we are on a raspi where we do not support graphical
|
||||
unset DISPLAY
|
||||
fi
|
||||
|
||||
if [ x$DISPLAY != x ] ; then
|
||||
# we have DISPLAY, do the graphic thing
|
||||
need python3-tk
|
||||
need python3.8-venv
|
||||
mkdir -p ~/ex-installer/venv
|
||||
python3 -m venv ~/ex-installer/venv
|
||||
cd ~/ex-installer/venv || exit 255
|
||||
source ./bin/activate
|
||||
git clone https://github.com/DCC-EX/EX-Installer
|
||||
cd EX-Installer || exit 255
|
||||
pip3 install -r requirements.txt
|
||||
exec python3 -m ex_installer
|
||||
fi
|
||||
if test -d `basename "$DCCEXGITURL"` ; then
|
||||
: assume we are almost there
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
|
@@ -24,6 +24,7 @@
|
||||
//#include "IO_TouchKeypad.h // Touch keypad with 16 keys
|
||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||
//#include "IO_EXFastClock.h" // FastClock driver
|
||||
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
|
||||
|
||||
//==========================================================================
|
||||
// The function halSetup() is invoked from CS if it exists within the build.
|
||||
@@ -51,7 +52,7 @@ void halSetup() {
|
||||
// Create a 20x4 LCD display device as display number 2
|
||||
// (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")').
|
||||
|
||||
// HALDisplay<LiquidCrystal>(2, 0x27, 20, 4);
|
||||
// HALDisplay<LiquidCrystal>::create(2, 0x27, 20, 4);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
|
@@ -173,6 +173,8 @@ board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F411RE]
|
||||
platform = ststm32
|
||||
|
32
version.h
32
version.h
@@ -3,8 +3,36 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.56"
|
||||
#define VERSION "5.0.8"
|
||||
// 5.0.8 - Bugfix: Do not crash on turnouts without description
|
||||
// 5.0.7 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
|
||||
// 5.0.6 - Bugfix lost TURNOUTL description
|
||||
// 5.0.5 - Bugfix version detection logic and better message
|
||||
// 5.0.4 - Bugfix: <JR> misses default roster.
|
||||
// 5.0.3 - Check bad AT firmware version
|
||||
// 5.0.2 - Bugfix: ESP32 30ms off time
|
||||
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
|
||||
// 5.0.0 - Make 4.2.69 the 5.0.0 release
|
||||
// 4.2.69 - Bugfix: Make <!> work in DC mode
|
||||
// 4.2.68 - Rename track mode OFF to NONE
|
||||
// 4.2.67 - AVR: Pin specific timer register seting
|
||||
// - Protect Uno user from choosing DC(X)
|
||||
// - More Nucleo variant defines
|
||||
// - GPIO PCA9555 / TCA9555 support
|
||||
// 4.2.66 - Throttle inrush current by applying PWM to brake pin when
|
||||
// fault pin goes active
|
||||
// 4.2.65 - new config WIFI_FORCE_AP option
|
||||
// 4.2.63 - completely new overcurrent detection
|
||||
// - ESP32 protect from race in RMT code
|
||||
// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position
|
||||
// - Update IO_EXTurntable.h to remove forced I2C clock speed
|
||||
// - Show device offline if EX-Turntable not connected
|
||||
// 4.2.61 - MAX_CURRENT restriction (caps motor shield value)
|
||||
// 4.2.60 - Add mDNS capability to ESP32 for autodiscovery
|
||||
// 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_
|
||||
// 4.2.58 - Start motordriver as soon as possible but without waveform
|
||||
// 4.2.57 - New overload handling (faster and handles commonFaultPin again)
|
||||
// - Optimize analog read STM32
|
||||
// 4.2.56 - Update IO_RotaryEncoder.h:
|
||||
// - Improved I2C communication, non-blocking reads
|
||||
// - Enable sending positions to the encoder from EXRAIL via SERVO()
|
||||
|
Reference in New Issue
Block a user