mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-12-23 21:01:25 +01:00
Merge pull request #399 from DCC-EX/devel-Ash
Update recent changes from devel branch
This commit is contained in:
commit
ab1a13775e
@ -312,6 +312,11 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||
broadcastReply(type, F("%s"),msg);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastMessage(char * message) {
|
||||
broadcastReply(COMMAND_TYPE, F("<m \"%s\">\n"),message);
|
||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%s\n"),message);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format, byte trackLetter, const FSH *modename, int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format, trackLetter, modename, dcAddr);
|
||||
}
|
||||
|
@ -60,6 +60,7 @@ public :
|
||||
static void forget(byte clientId);
|
||||
static void broadcastRouteState(uint16_t routeId,byte state);
|
||||
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
|
||||
static void broadcastMessage(char * message);
|
||||
|
||||
// Handling code for virtual LCD receiver.
|
||||
static Print * getVirtualLCDSerial(byte screen, byte row);
|
||||
|
@ -65,6 +65,9 @@
|
||||
#ifdef EXRAIL_WARNING
|
||||
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
|
||||
#endif
|
||||
// compile time check, passwords 1 to 7 chars do not work, so do not try to compile with them at all
|
||||
// remember trailing '\0', sizeof("") == 1.
|
||||
#define PASSWDCHECK(S) static_assert(sizeof(S) == 1 || sizeof(S) > 8, "Password shorter than 8 chars")
|
||||
|
||||
void setup()
|
||||
{
|
||||
@ -102,10 +105,12 @@ void setup()
|
||||
// Start Ethernet if it exists
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
PASSWDCHECK(WIFI_PASSWORD); // compile time check
|
||||
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
|
||||
PASSWDCHECK(WIFI_PASSWORD); // compile time check
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
|
51
DCC.cpp
51
DCC.cpp
@ -305,6 +305,57 @@ void DCC::setAccessory(int address, byte port, bool gate, byte onoff /*= 2*/) {
|
||||
}
|
||||
}
|
||||
|
||||
bool DCC::setExtendedAccessory(int16_t address, int16_t value, byte repeats) {
|
||||
|
||||
/* From https://www.nmra.org/sites/default/files/s-9.2.1_2012_07.pdf
|
||||
|
||||
The Extended Accessory Decoder Control Packet is included for the purpose of transmitting aspect control to signal
|
||||
decoders or data bytes to more complex accessory decoders. Each signal head can display one aspect at a time.
|
||||
{preamble} 0 10AAAAAA 0 0AAA0AA1 0 000XXXXX 0 EEEEEEEE 1
|
||||
|
||||
XXXXX is for a single head. A value of 00000 for XXXXX indicates the absolute stop aspect. All other aspects
|
||||
represented by the values for XXXXX are determined by the signaling system used and the prototype being
|
||||
modeled.
|
||||
|
||||
From https://normen.railcommunity.de/RCN-213.pdf:
|
||||
|
||||
More information is in RCN-213 about how the address bits are organized.
|
||||
preamble -0- 1 0 A7 A6 A5 A4 A3 A2 -0- 0 ^A10 ^A9 ^A8 0 A1 A0 1 -0- ....
|
||||
|
||||
Thus in byte packet form the format is 10AAAAAA, 0AAA0AA1, 000XXXXX
|
||||
|
||||
Die Adresse für den ersten erweiterten Zubehördecoder ist wie bei den einfachen
|
||||
Zubehördecodern die Adresse 4 = 1000-0001 0111-0001 . Diese Adresse wird in
|
||||
Anwenderdialogen als Adresse 1 dargestellt.
|
||||
|
||||
This means that the first address shown to the user as "1" is mapped
|
||||
to internal address 4.
|
||||
|
||||
Note that the Basic accessory format mentions "By convention these
|
||||
bits (bits 4-6 of the second data byte) are in ones complement" but
|
||||
this note is absent from the advanced packet description. The
|
||||
english translation does not mention that the address format for
|
||||
the advanced packet follows the one for the basic packet but
|
||||
according to the RCN-213 this is the case.
|
||||
|
||||
We allow for addresses from -3 to 2047-3 as that allows to address the
|
||||
whole range of the 11 bits sent to track.
|
||||
*/
|
||||
if ((address > 2044) || (address < -3)) return false; // 2047-3, 11 bits but offset 3
|
||||
if (value != (value & 0x1F)) return false; // 5 bits
|
||||
|
||||
address+=3; // +3 offset according to RCN-213
|
||||
byte b[3];
|
||||
b[0]= 0x80 // bits always on
|
||||
| ((address>>2) & 0x3F); // shift out 2, mask out used bits
|
||||
b[1]= 0x01 // bits always on
|
||||
| (((~(address>>8)) & 0x07)<<4) // shift out 8, invert, mask 3 bits, shift up 4
|
||||
| ((address & 0x03)<<1); // mask 2 bits, shift up 1
|
||||
b[2]=value;
|
||||
DCCWaveform::mainTrack.schedulePacket(b, sizeof(b), repeats);
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// writeCVByteMain: Write a byte with PoM on main. This writes
|
||||
// the 5 byte sized packet to implement this DCC function
|
||||
|
1
DCC.h
1
DCC.h
@ -72,6 +72,7 @@ public:
|
||||
static uint32_t getFunctionMap(int cab);
|
||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
|
||||
static bool setExtendedAccessory(int16_t address, int16_t value, byte repeats=3);
|
||||
static bool writeTextPacket(byte *b, int nBytes);
|
||||
|
||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||
|
@ -45,7 +45,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
0, Track power off
|
||||
1, Track power on
|
||||
a, DCC accessory control
|
||||
A,
|
||||
A, DCC extended accessory control
|
||||
b, Write CV bit on main
|
||||
B, Write CV bit
|
||||
c, Request current command
|
||||
@ -384,6 +384,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
#endif
|
||||
}
|
||||
return;
|
||||
|
||||
case 'A': // EXTENDED ACCESSORY <A address value>
|
||||
// Note: if this happens to match a defined EXRAIL
|
||||
// DCCX_SIGNAL, then EXRAIL will have intercepted
|
||||
// this command alrerady.
|
||||
if (params==2 && DCC::setExtendedAccessory(p[0],p[1])) return;
|
||||
break;
|
||||
|
||||
case 'T': // TURNOUT <T ...>
|
||||
if (parseT(stream, params, p))
|
||||
@ -1036,7 +1043,32 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||
DCC::setGlobalSpeedsteps(128);
|
||||
DIAG(F("128 Speedsteps"));
|
||||
return true;
|
||||
|
||||
#if defined(HAS_ENOUGH_MEMORY) && !defined(ARDUINO_ARCH_UNO)
|
||||
case "RAILCOM"_hk:
|
||||
{ // <C RAILCOM ON|OFF|DEBUG >
|
||||
if (params<2) return false;
|
||||
bool on=false;
|
||||
bool debug=false;
|
||||
switch (p[1]) {
|
||||
case "ON"_hk:
|
||||
case 1:
|
||||
on=true;
|
||||
break;
|
||||
case "DEBUG"_hk:
|
||||
on=true;
|
||||
debug=true;
|
||||
break;
|
||||
case "OFF"_hk:
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
DIAG(F("Railcom %S")
|
||||
,DCCWaveform::setRailcom(on,debug)?F("ON"):F("OFF"));
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifndef DISABLE_PROG
|
||||
case "ACK"_hk: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
||||
if (params >= 3) {
|
||||
|
@ -62,6 +62,8 @@ class DCCTimer {
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
static void clearPWM();
|
||||
static void startRailcomTimer(byte brakePin);
|
||||
static void ackRailcomTimer();
|
||||
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||
static void DCCEXanalogWrite(uint8_t pin, int value);
|
||||
|
||||
|
@ -40,6 +40,9 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||
#define TIMER1_A_PIN 11
|
||||
#define TIMER1_B_PIN 12
|
||||
#define TIMER1_C_PIN 13
|
||||
#define TIMER2_A_PIN 10
|
||||
#define TIMER2_B_PIN 9
|
||||
|
||||
#else
|
||||
#define TIMER1_A_PIN 9
|
||||
#define TIMER1_B_PIN 10
|
||||
@ -56,6 +59,67 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interrupts();
|
||||
}
|
||||
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
/* The Railcom timer is started in such a way that it
|
||||
- First triggers 28uS after the last TIMER1 tick.
|
||||
This provides an accurate offset (in High Accuracy mode)
|
||||
for the start of the Railcom cutout.
|
||||
- Sets the Railcom pin high at first tick,
|
||||
because its been setup with 100% PWM duty cycle.
|
||||
|
||||
- Cycles at 436uS so the second tick is the
|
||||
correct distance from the cutout.
|
||||
|
||||
- Waveform code is responsible for altering the PWM
|
||||
duty cycle to 0% any time between the first and last tick.
|
||||
(there will be 7 DCC timer1 ticks in which to do this.)
|
||||
|
||||
*/
|
||||
(void) brakePin; // Ignored... works on pin 9 only
|
||||
const int cutoutDuration = 430; // Desired interval in microseconds
|
||||
|
||||
// Set up Timer2 for CTC mode (Clear Timer on Compare Match)
|
||||
TCCR2A = 0; // Clear Timer2 control register A
|
||||
TCCR2B = 0; // Clear Timer2 control register B
|
||||
TCNT2 = 0; // Initialize Timer2 counter value to 0
|
||||
// Configure Phase and Frequency Correct PWM mode
|
||||
TCCR2A = (1 << COM2B1); // enable pwm on pin 9
|
||||
TCCR2A |= (1 << WGM20);
|
||||
|
||||
|
||||
// Set Timer 2 prescaler to 32
|
||||
TCCR2B = (1 << CS21) | (1 << CS20); // 32 prescaler
|
||||
|
||||
// Set the compare match value for desired interval
|
||||
OCR2A = (F_CPU / 1000000) * cutoutDuration / 64 - 1;
|
||||
|
||||
// Calculate the compare match value for desired duty cycle
|
||||
OCR2B = OCR2A+1; // set duty cycle to 100%= OCR2A)
|
||||
|
||||
// Enable Timer2 output on pin 9 (OC2B)
|
||||
DDRB |= (1 << DDB1);
|
||||
// TODO Fudge TCNT2 to sync with last tcnt1 tick + 28uS
|
||||
|
||||
// Previous TIMER1 Tick was at rising end-of-packet bit
|
||||
// Cutout starts half way through first preamble
|
||||
// that is 2.5 * 58uS later.
|
||||
// TCNT1 ticks 8 times / microsecond
|
||||
// auto microsendsToFirstRailcomTick=(58+58+29)-(TCNT1/8);
|
||||
// set the railcom timer counter allowing for phase-correct
|
||||
|
||||
// CHris's NOTE:
|
||||
// I dont kniow quite how this calculation works out but
|
||||
// it does seems to get a good answer.
|
||||
|
||||
TCNT2=193 + (ICR1 - TCNT1)/8;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
OCR2B= 0x00; // brake pin pwm duty cycle 0 at next tick
|
||||
}
|
||||
|
||||
|
||||
// ISR called by timer interrupt every 58uS
|
||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||
|
||||
|
@ -80,6 +80,15 @@ extern char *__malloc_heap_start;
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
(void) pin;
|
||||
return false; // TODO what are the relevant pins?
|
||||
|
@ -76,6 +76,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interrupts();
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
|
||||
// copied from rf24 branch
|
||||
void TCC0_Handler() {
|
||||
|
@ -201,6 +201,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interrupts();
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
|
@ -39,6 +39,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
||||
}
|
||||
|
||||
void DCCTimer::startRailcomTimer(byte brakePin) {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
(void) brakePin;
|
||||
}
|
||||
|
||||
void DCCTimer::ackRailcomTimer() {
|
||||
// TODO: for intended operation see DCCTimerAVR.cpp
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//Teensy: digitalPinHasPWM, todo
|
||||
(void) pin;
|
||||
|
@ -115,8 +115,22 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||
bytes_sent = 0;
|
||||
bits_sent = 0;
|
||||
}
|
||||
|
||||
volatile bool DCCWaveform::railcomActive=false; // switched on by user
|
||||
volatile bool DCCWaveform::railcomDebug=false; // switched on by user
|
||||
|
||||
|
||||
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||
if (on) {
|
||||
// TODO check possible
|
||||
railcomActive=true;
|
||||
railcomDebug=debug;
|
||||
}
|
||||
else {
|
||||
railcomActive=false;
|
||||
railcomDebug=false;
|
||||
}
|
||||
return railcomActive;
|
||||
}
|
||||
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
@ -124,16 +138,16 @@ void DCCWaveform::interrupt2() {
|
||||
// calculate the next bit to be sent:
|
||||
// set state WAVE_MID_1 for a 1=bit
|
||||
// or WAVE_HIGH_0 for a 0 bit.
|
||||
|
||||
if (remainingPreambles > 0 ) {
|
||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||
remainingPreambles--;
|
||||
|
||||
|
||||
// As we get to the end of the preambles, open the reminder window.
|
||||
// This delays any reminder insertion until the last moment so
|
||||
// that the reminder doesn't block a more urgent packet.
|
||||
reminderWindowOpen=transmitRepeats==0 && remainingPreambles<4 && remainingPreambles>1;
|
||||
if (remainingPreambles==1) promotePendingPacket();
|
||||
else if (remainingPreambles==10 && isMainTrack && railcomActive) DCCTimer::ackRailcomTimer();
|
||||
// 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.
|
||||
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||
@ -157,6 +171,12 @@ void DCCWaveform::interrupt2() {
|
||||
bytes_sent = 0;
|
||||
// preamble for next packet will start...
|
||||
remainingPreambles = requiredPreambles;
|
||||
|
||||
// set the railcom coundown to trigger half way
|
||||
// through the first preamble bit.
|
||||
// Note.. we are still sending the last packet bit
|
||||
// and we then have to allow for the packet end bit
|
||||
if (isMainTrack && railcomActive) DCCTimer::startRailcomTimer(9);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -208,7 +228,11 @@ void DCCWaveform::promotePendingPacket() {
|
||||
|
||||
// nothing to do, just send idles or resets
|
||||
// Fortunately reset and idle packets are the same length
|
||||
memcpy( transmitPacket, isMainTrack ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
// Note: If railcomDebug is on, then we send resets to the main
|
||||
// track instead of idles. This means that all data will be zeros
|
||||
// and only the porersets will be ones, making it much
|
||||
// easier to read on a logic analyser.
|
||||
memcpy( transmitPacket, (isMainTrack && (!railcomDebug)) ? idlePacket : resetPacket, sizeof(idlePacket));
|
||||
transmitLength = sizeof(idlePacket);
|
||||
transmitRepeats = 0;
|
||||
if (getResets() < 250) sentResetsSincePacket++; // only place to increment (private!)
|
||||
@ -297,4 +321,10 @@ bool DCCWaveform::isReminderWindowOpen() {
|
||||
void IRAM_ATTR DCCWaveform::loop() {
|
||||
DCCACK::checkAck(progTrack.getResets());
|
||||
}
|
||||
|
||||
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||
// TODO... ESP32 railcom waveform
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -40,7 +40,14 @@ const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload s
|
||||
|
||||
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||
// to the transform array.
|
||||
enum WAVE_STATE : byte {WAVE_START=0,WAVE_MID_1=1,WAVE_HIGH_0=2,WAVE_MID_0=3,WAVE_LOW_0=4,WAVE_PENDING=5};
|
||||
enum WAVE_STATE : byte {
|
||||
WAVE_START=0, // wave going high at start of bit
|
||||
WAVE_MID_1=1, // middle of 1 bit
|
||||
WAVE_HIGH_0=2, // first part of 0 bit high
|
||||
WAVE_MID_0=3, // middle of 0 bit
|
||||
WAVE_LOW_0=4, // first part of 0 bit low
|
||||
WAVE_PENDING=5 // next bit not yet known
|
||||
};
|
||||
|
||||
// NOTE: static functions are used for the overall controller, then
|
||||
// one instance is created for each track.
|
||||
@ -78,6 +85,8 @@ class DCCWaveform {
|
||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||
bool isReminderWindowOpen();
|
||||
void promotePendingPacket();
|
||||
static bool setRailcom(bool on, bool debug);
|
||||
static bool isRailcom() {return railcomActive;}
|
||||
|
||||
private:
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
@ -103,6 +112,9 @@ class DCCWaveform {
|
||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||
byte pendingLength;
|
||||
byte pendingRepeats;
|
||||
static volatile bool railcomActive; // switched on by user
|
||||
static volatile bool railcomDebug; // switched on by user
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static RMTChannel *rmtMainChannel;
|
||||
static RMTChannel *rmtProgChannel;
|
||||
|
56
EXRAIL2.cpp
56
EXRAIL2.cpp
@ -839,6 +839,14 @@ void RMFT2::loop2() {
|
||||
DCC::setAccessory(addr,subaddr,active);
|
||||
break;
|
||||
}
|
||||
case OPCODE_ASPECT: {
|
||||
// operand is address<<5 | value
|
||||
int16_t address=operand>>5;
|
||||
byte aspect=operand & 0x1f;
|
||||
if (!signalAspectEvent(address,aspect))
|
||||
DCC::setExtendedAccessory(address,aspect);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_FOLLOW:
|
||||
progCounter=routeLookup->find(operand);
|
||||
@ -1100,7 +1108,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||
|
||||
// Schedule any event handler for this signal change.
|
||||
// Thjis will work even without a signal definition.
|
||||
// This will work even without a signal definition.
|
||||
if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id);
|
||||
else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
|
||||
else onAmberLookup->handleEvent(F("AMBER"),id);
|
||||
@ -1137,6 +1145,16 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (sigtype== DCCX_SIGNAL_FLAG) {
|
||||
// redpin,amberpin,greenpin are the 3 aspects
|
||||
byte value=redpin;
|
||||
if (rag==SIGNAL_AMBER) value=amberpin;
|
||||
if (rag==SIGNAL_GREEN) value=greenpin;
|
||||
DCC::setExtendedAccessory(sigid & SIGNAL_ID_MASK,value);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// LED or similar 3 pin signal, (all pins zero would be a virtual signal)
|
||||
// If amberpin is zero, synthesise amber from red+green
|
||||
const byte SIMAMBER=0x00;
|
||||
@ -1170,6 +1188,38 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
return (flags[sigslot] & SIGNAL_MASK) == rag;
|
||||
}
|
||||
|
||||
|
||||
// signalAspectEvent returns true if the aspect is destined
|
||||
// for a defined DCCX_SIGNAL which will handle all the RAG flags
|
||||
// and ON* handlers.
|
||||
// Otherwise false so the parser should send the command directly
|
||||
bool RMFT2::signalAspectEvent(int16_t address, byte aspect ) {
|
||||
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
|
||||
int16_t sigslot=getSignalSlot(address);
|
||||
if (sigslot<0) return false; // this is not a defined signal
|
||||
int16_t sigpos=sigslot*8;
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
|
||||
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
|
||||
if (sigtype!=DCCX_SIGNAL_FLAG) return false; // not a DCCX signal
|
||||
// Turn an aspect change into a RED/AMBER/GREEN setting
|
||||
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2)) {
|
||||
doSignal(sigid,SIGNAL_RED);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4)) {
|
||||
doSignal(sigid,SIGNAL_AMBER);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (aspect==GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6)) {
|
||||
doSignal(sigid,SIGNAL_GREEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false; // aspect is not a defined one
|
||||
}
|
||||
|
||||
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
||||
// Hunt for an ONTHROW/ONCLOSE for this turnout
|
||||
if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
|
||||
@ -1284,6 +1334,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
break;
|
||||
case thrunge_parse:
|
||||
case thrunge_broadcast:
|
||||
case thrunge_message:
|
||||
case thrunge_lcd:
|
||||
default: // thrunge_lcd+1, ...
|
||||
if (!buffer) buffer=new StringBuffer();
|
||||
@ -1321,6 +1372,9 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
case thrunge_withrottle:
|
||||
CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString());
|
||||
break;
|
||||
case thrunge_message:
|
||||
CommandDistributor::broadcastMessage(buffer->getString());
|
||||
break;
|
||||
case thrunge_lcd:
|
||||
LCD(id,F("%s"),buffer->getString());
|
||||
break;
|
||||
|
@ -54,7 +54,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SETFREQ,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,OPCODE_ASPECT,
|
||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
@ -94,7 +94,7 @@ enum thrunger: byte {
|
||||
thrunge_serial,thrunge_parse,
|
||||
thrunge_serial1, thrunge_serial2, thrunge_serial3,
|
||||
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
||||
thrunge_lcn,
|
||||
thrunge_lcn,thrunge_message,
|
||||
thrunge_lcd, // Must be last!!
|
||||
};
|
||||
|
||||
@ -155,9 +155,11 @@ class LookList {
|
||||
static void clockEvent(int16_t clocktime, bool change);
|
||||
static void rotateEvent(int16_t id, bool change);
|
||||
static void powerEvent(int16_t track, bool overload);
|
||||
static bool signalAspectEvent(int16_t address, byte aspect );
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||
static const int16_t DCCX_SIGNAL_FLAG=0x3000;
|
||||
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
||||
// Throttle Info Access functions built by exrail macros
|
||||
static const byte rosterNameCount;
|
||||
@ -172,7 +174,7 @@ class LookList {
|
||||
static const FSH * getTurntableDescription(int16_t id);
|
||||
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
||||
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
|
||||
|
||||
|
||||
private:
|
||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||
|
@ -31,6 +31,7 @@
|
||||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef ANOUT
|
||||
#undef ASPECT
|
||||
#undef AT
|
||||
#undef ATGTE
|
||||
#undef ATLT
|
||||
@ -42,7 +43,9 @@
|
||||
#undef CLEAR_STASH
|
||||
#undef CLEAR_ALL_STASH
|
||||
#undef CLOSE
|
||||
#undef CONFIGURE_SERVO
|
||||
#undef DCC_SIGNAL
|
||||
#undef DCCX_SIGNAL
|
||||
#undef DCC_TURNTABLE
|
||||
#undef DEACTIVATE
|
||||
#undef DEACTIVATEL
|
||||
@ -94,6 +97,7 @@
|
||||
#undef LCCX
|
||||
#undef LCN
|
||||
#undef MOVETT
|
||||
#undef MESSAGE
|
||||
#undef ONACTIVATE
|
||||
#undef ONACTIVATEL
|
||||
#undef ONAMBER
|
||||
@ -186,6 +190,7 @@
|
||||
#define AMBER(signal_id)
|
||||
#define ANOUT(vpin,value,param1,param2)
|
||||
#define AT(sensor_id)
|
||||
#define ASPECT(address,value)
|
||||
#define ATGTE(sensor_id,value)
|
||||
#define ATLT(sensor_id,value)
|
||||
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||
@ -195,8 +200,10 @@
|
||||
#define CALL(route)
|
||||
#define CLEAR_STASH(id)
|
||||
#define CLEAR_ALL_STASH(id)
|
||||
#define CLOSE(id)
|
||||
#define CLOSE(id)
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect)
|
||||
#define DCC_TURNTABLE(id,home,description)
|
||||
#define DEACTIVATE(addr,subaddr)
|
||||
#define DEACTIVATEL(addr)
|
||||
@ -247,6 +254,7 @@
|
||||
#define LCD(row,msg)
|
||||
#define SCREEN(display,row,msg)
|
||||
#define LCN(msg)
|
||||
#define MESSAGE(msg)
|
||||
#define MOVETT(id,steps,activity)
|
||||
#define ONACTIVATE(addr,subaddr)
|
||||
#define ONACTIVATEL(linear)
|
||||
|
@ -51,6 +51,14 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
|
||||
opcode=0;
|
||||
break;
|
||||
|
||||
case 'A': // <A address aspect>
|
||||
if (paramCount!=2) break;
|
||||
// Ask exrail if this is just changing the aspect on a
|
||||
// predefined DCCX_SIGNAL. Because this will handle all
|
||||
// the IFRED and ONRED type issues at the same time.
|
||||
if (signalAspectEvent(p[0],p[1])) opcode=0; // all done
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
// This entire code block is compiled out if LLC macros not used
|
||||
if (!(compileFeatures & FEATURE_LCC)) return;
|
||||
|
@ -95,14 +95,14 @@ constexpr int16_t stuffSize=sizeof(compileTimeSequenceList)/sizeof(int16_t) - 1;
|
||||
|
||||
|
||||
// Compile time function to check for sequence nos.
|
||||
constexpr bool hasseq(const int16_t value, const uint16_t pos=0 ) {
|
||||
constexpr bool hasseq(const int16_t value, const int16_t pos=0 ) {
|
||||
return pos>=stuffSize? false :
|
||||
compileTimeSequenceList[pos]==value
|
||||
|| hasseq(value,pos+1);
|
||||
}
|
||||
|
||||
// Compile time function to check for duplicate sequence nos.
|
||||
constexpr bool hasdup(const int16_t value, const uint16_t pos ) {
|
||||
constexpr bool hasdup(const int16_t value, const int16_t pos ) {
|
||||
return pos>=stuffSize? false :
|
||||
compileTimeSequenceList[pos]==value
|
||||
|| hasseq(value,pos+1)
|
||||
@ -117,6 +117,9 @@ static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AU
|
||||
// - check range on LATCH/UNLATCH
|
||||
// This pass generates no runtime data or code
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ASPECT
|
||||
#define ASPECT(address,value) static_assert(address <=2044, "invalid Address"); \
|
||||
static_assert(address>=-3, "Invalid value");
|
||||
#undef CALL
|
||||
#define CALL(id) static_assert(hasseq(id),"Sequence not found");
|
||||
#undef FOLLOW
|
||||
@ -151,6 +154,8 @@ static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AU
|
||||
#define HAL_IGNORE_DEFAULTS ignore_defaults=true;
|
||||
#undef JMRI_SENSOR
|
||||
#define JMRI_SENSOR(vpin,count...) Sensor::createMultiple(vpin,##count);
|
||||
#undef CONFIGURE_SERVO
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile) IODevice::configureServo(vpin,pos1,pos2,PCA9685::profile);
|
||||
bool exrailHalSetup() {
|
||||
bool ignore_defaults=false;
|
||||
#include "myAutomation.h"
|
||||
@ -167,6 +172,8 @@ bool exrailHalSetup() {
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) | FEATURE_SIGNAL
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
|
||||
#undef DCCX_SIGNAL
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) | FEATURE_SIGNAL
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
|
||||
|
||||
@ -246,6 +253,9 @@ const int StringMacroTracker1=__COUNTER__;
|
||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||
#undef LCN
|
||||
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
|
||||
#undef MESSAGE
|
||||
#define MESSAGE(msg) THRUNGE(msg,thrunge_message)
|
||||
|
||||
#undef ROUTE_CAPTION
|
||||
#define ROUTE_CAPTION(id,caption) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
@ -343,6 +353,8 @@ const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
|
||||
#define TT_ADDPOSITION(turntable_id,position,value,home,description...) T_DESC(turntable_id,position,description)
|
||||
|
||||
const FSH * RMFT2::getTurntablePositionDescription(int16_t turntableId, uint8_t positionId) {
|
||||
(void)turntableId;
|
||||
(void)positionId;
|
||||
#include "myAutomation.h"
|
||||
return NULL;
|
||||
}
|
||||
@ -396,6 +408,8 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
|
||||
#undef DCCX_SIGNAL
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect) id | RMFT2::DCCX_SIGNAL_FLAG,redAspect,amberAspect,greenAspect,
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
||||
|
||||
@ -430,6 +444,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
|
||||
#define ASPECT(address,value) OPCODE_ASPECT,V((address<<5) | (value & 0x1F)),
|
||||
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
@ -441,6 +456,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define CLEAR_STASH(id) OPCODE_CLEAR_STASH,V(id),
|
||||
#define CLEAR_ALL_STASH OPCODE_CLEAR_ALL_STASH,V(0),
|
||||
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
#ifndef IO_NO_HAL
|
||||
#define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home),
|
||||
#endif
|
||||
@ -450,6 +466,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
||||
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect)
|
||||
#define DONE OPCODE_ENDTASK,0,0,
|
||||
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
|
||||
#define ELSE OPCODE_ELSE,0,0,
|
||||
@ -502,6 +519,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define SCREEN(display,id,msg) PRINT(msg)
|
||||
#define STEALTH(code...) PRINT(dummy)
|
||||
#define LCN(msg) PRINT(msg)
|
||||
#define MESSAGE(msg) PRINT(msg)
|
||||
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
|
||||
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
|
@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-stm32EC 202402072340Z"
|
||||
#define GITHUB_SHA "devel-stm32ECa-202403082308Z"
|
||||
|
@ -83,6 +83,7 @@ void EXTurntable::_loop(unsigned long currentMicros) {
|
||||
// Read returns status as obtained in our loop.
|
||||
// Return false if our status value is invalid.
|
||||
int EXTurntable::_read(VPIN vpin) {
|
||||
(void)vpin; // surpress warning
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
if (_stepperStatus > 1) {
|
||||
return false;
|
||||
@ -127,6 +128,8 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
vpin, value, activity, duration);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||
#else
|
||||
(void)duration;
|
||||
#endif
|
||||
if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
_previousStatus = _stepperStatus;
|
||||
|
@ -204,7 +204,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
||||
}
|
||||
|
||||
|
||||
|
@ -34,9 +34,15 @@ template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
|
||||
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
|
||||
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
|
||||
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, TRACK_MODE_BOOST = 32,
|
||||
TRACK_MODE_ALL = 62, // only to operate all tracks
|
||||
TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72 /*DC + INV*/, TRACK_MODE_AUTOINV = 128};
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16,
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
TRACK_MODE_BOOST = 32,
|
||||
#else
|
||||
TRACK_MODE_BOOST = 0,
|
||||
#endif
|
||||
TRACK_MODE_ALL = TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_EXT|TRACK_MODE_BOOST,
|
||||
TRACK_MODE_INV = 64,
|
||||
TRACK_MODE_DCX = TRACK_MODE_DC|TRACK_MODE_INV, TRACK_MODE_AUTOINV = 128};
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
|
@ -158,12 +158,6 @@ void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
(void) on;
|
||||
// TODO Cutout needs fake ports as well
|
||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
||||
}
|
||||
|
||||
// setPROGSignal(), called from interrupt context
|
||||
// does assume ports are shadowed if they can be
|
||||
void TrackManager::setPROGSignal( bool on) {
|
||||
@ -225,7 +219,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
if (mode & TRACK_MODE_BOOST) {
|
||||
//DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin);
|
||||
pinMode(BOOSTER_INPUT, INPUT);
|
||||
gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback
|
||||
gpio_matrix_in(BOOSTER_INPUT, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback
|
||||
gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false);
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false);
|
||||
@ -368,7 +362,8 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
if (params==2 && p[1]=="EXT"_hk) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
||||
if (TRACK_MODE_BOOST != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
||||
#endif
|
||||
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
|
||||
@ -407,11 +402,11 @@ const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
||||
modename=F("EXT");
|
||||
else if(tm & TRACK_MODE_BOOST) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
modename=F("B A");
|
||||
modename=F("BOOST A");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
modename=F("B I");
|
||||
modename=F("BOOST I");
|
||||
else
|
||||
modename=F("B");
|
||||
modename=F("BOOST");
|
||||
}
|
||||
else if (tm & TRACK_MODE_DC) {
|
||||
if (tm & TRACK_MODE_INV)
|
||||
|
@ -57,7 +57,6 @@ 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 MotorDriver * getProgDriver();
|
||||
|
@ -247,22 +247,23 @@ DCCTurntable::DCCTurntable(uint16_t id) : Turntable(id, TURNTABLE_DCC) {}
|
||||
StringFormatter::send(stream, F("<i %d DCCTURNTABLE>\n"), _turntableData.id);
|
||||
}
|
||||
|
||||
// EX-Turntable specific code for moving to the specified position
|
||||
bool DCCTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
|
||||
// EX-Turntable specific code for moving to the specified position
|
||||
bool DCCTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
|
||||
(void) activity;
|
||||
#ifndef IO_NO_HAL
|
||||
int16_t value = getPositionValue(position);
|
||||
if (position == 0 || !value) return false; // Return false if it's not a valid position
|
||||
// Set position via device driver
|
||||
int16_t addr=value>>3;
|
||||
int16_t subaddr=(value>>1) & 0x03;
|
||||
bool active=value & 0x01;
|
||||
_previousPosition = _turntableData.position;
|
||||
_turntableData.position = position;
|
||||
DCC::setAccessory(addr, subaddr, active);
|
||||
int16_t value = getPositionValue(position);
|
||||
if (position == 0 || !value) return false; // Return false if it's not a valid position
|
||||
// Set position via device driver
|
||||
int16_t addr=value>>3;
|
||||
int16_t subaddr=(value>>1) & 0x03;
|
||||
bool active=value & 0x01;
|
||||
_previousPosition = _turntableData.position;
|
||||
_turntableData.position = position;
|
||||
DCC::setAccessory(addr, subaddr, active);
|
||||
#else
|
||||
(void)position;
|
||||
(void)position;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -148,10 +148,7 @@ build_flags =
|
||||
platform = atmelavr
|
||||
board = uno
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
${env.lib_deps}
|
||||
arduino-libraries/Ethernet
|
||||
SPI
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -mcall-prologues
|
||||
@ -164,6 +161,7 @@ framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -mcall-prologues
|
||||
|
||||
[env:ESP32]
|
||||
platform = espressif32
|
||||
|
18
version.h
18
version.h
@ -3,7 +3,22 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.3.5e"
|
||||
#define VERSION "5.3.6"
|
||||
// 5.2.38 - Exrail MESSAGE("text") to send a user message to all
|
||||
// connected throttles (uses <m "text"> and withrottle Hmtext.
|
||||
// 5.2.37 - Bugfix ESP32: Use BOOSTER_INPUT define
|
||||
// 5.2.36 - Variable frequency for DC mode
|
||||
// 5.2.35 - Bugfix: Make DCC Extended Accessories follow RCN-213
|
||||
// 5.2.34 - <A address aspect> Command fopr DCC Extended Accessories
|
||||
// - Exrail ASPECT(address,aspect) for above.
|
||||
// - EXRAIL DCCX_SIGNAL(Address,redAspect,amberAspect,greenAspect)
|
||||
// - Exrail intercept <A ...> for DCC Signals.
|
||||
// 5.2.33 - Exrail CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
// 5.2.32 - Railcom Cutout (Initial trial Mega2560 only)
|
||||
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
|
||||
// 5.2.30 - Bugfix: WiThrottle sendIntro after initial N message as well
|
||||
//
|
||||
//
|
||||
// 5.3.5e - Fixes to ethernet cable handling, and STM32 related handling as well
|
||||
// 5.3.5 - Exrail JMRI_SENSORS(vpin [,count]) creates <S> types.
|
||||
// 5.3.4 - Bugfix: WiThrottle sendIntro after initial N message as well
|
||||
@ -12,6 +27,7 @@
|
||||
// 5.3.1 - Variable frequency for DC mode
|
||||
// 5.2.40 - Bugfix: WiThrottle sendIntro after initial N message as well
|
||||
// 5.2.31 - included in stm32EC as 5.3.5
|
||||
// -- some duplicates in above list
|
||||
// 5.2.29 - Added IO_I2CDFPlayer.h to support DFPLayer over I2C connected to NXP SC16IS750/SC16IS752 (currently only single UART for SC16IS752)
|
||||
// - Added enhanced IO_I2CDFPLayer enum commands to EXRAIL2.h
|
||||
// - Added PLAYSOUND alias of ANOUT to EXRAILMacros.h
|
||||
|
Loading…
Reference in New Issue
Block a user