mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
24 Commits
v5.1.11-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 |
@@ -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;
|
||||
|
@@ -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); \
|
||||
}
|
||||
|
||||
|
||||
@@ -656,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;
|
||||
@@ -912,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>
|
||||
|
126
DCCWaveform.cpp
126
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
|
||||
|
||||
|
@@ -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-202308041244Z"
|
||||
#define GITHUB_SHA "railcomtests-202401142146Z"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/* @ 2024 Arkadiusz Hahn
|
||||
* © 2022-2023 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
@@ -32,6 +32,7 @@ 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;
|
||||
@@ -170,6 +196,9 @@ bool MotorDriver::isPWMCapable() {
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
}
|
||||
|
||||
bool MotorDriver::isRailcomCapable() {
|
||||
return (!dualSignal) && (brakePin!=UNUSED_PIN);
|
||||
}
|
||||
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
if (powerMode == mode) return;
|
||||
@@ -195,23 +224,6 @@ 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;
|
||||
@@ -455,7 +467,7 @@ 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);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -43,6 +43,7 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||
#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
|
||||
@@ -74,7 +75,9 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||
#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
|
||||
@@ -110,6 +113,7 @@ 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, ALERT };
|
||||
|
||||
@@ -118,35 +122,59 @@ class MotorDriver {
|
||||
|
||||
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);
|
||||
@@ -178,6 +206,7 @@ class MotorDriver {
|
||||
return rawCurrentTripValue;
|
||||
}
|
||||
bool isPWMCapable();
|
||||
bool isRailcomCapable();
|
||||
bool canMeasureCurrent();
|
||||
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
|
||||
bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
@@ -219,7 +248,7 @@ class MotorDriver {
|
||||
bool isProgTrack = false; // tells us if this is a prog track
|
||||
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
|
||||
inline void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
getFastPin(type, pin, 0, result);
|
||||
getFastPin(type, pin, 0, result);
|
||||
};
|
||||
// side effect sets lastCurrent and tripValue
|
||||
inline bool checkCurrent(bool useProgLimit) {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
/*
|
||||
/* @ 2024 Arkadiusz Hahn
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
@@ -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
|
||||
@@ -127,10 +127,10 @@ void TrackManager::Setup(const FSH * shieldname,
|
||||
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');
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,10 +140,10 @@ void TrackManager::Setup(const FSH * shieldname,
|
||||
void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
track[t]=driver;
|
||||
if (driver) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
track[t]->setMode(TRACK_MODE_NONE);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,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
|
||||
@@ -505,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;
|
||||
}
|
||||
}
|
||||
|
@@ -51,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();
|
||||
|
@@ -200,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"));
|
||||
|
@@ -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);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
|
11
version.h
11
version.h
@@ -3,7 +3,16 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "4.2.69"
|
||||
#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
|
||||
|
Reference in New Issue
Block a user