mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-23 08:06:13 +01:00
Merge df358c0bc8
into 4e491a1e56
This commit is contained in:
commit
66c83ee3a6
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -13,3 +13,6 @@ myFilter.cpp
|
||||||
my*.h
|
my*.h
|
||||||
!my*.example.h
|
!my*.example.h
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
newcode.txt.old
|
||||||
|
UserAddin.txt
|
||||||
|
.readme.txt
|
||||||
|
|
|
@ -105,6 +105,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
|
||||||
void CommandDistributor::forget(byte clientId) {
|
void CommandDistributor::forget(byte clientId) {
|
||||||
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
||||||
clients[clientId]=NONE_TYPE;
|
clients[clientId]=NONE_TYPE;
|
||||||
|
if (virtualLCDClient==clientId) virtualLCDClient=RingStream::NO_CLIENT;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -161,6 +162,10 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<I %d %d %d>\n"), id, position, moving);
|
||||||
|
}
|
||||||
|
|
||||||
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||||
// The JMRI clock command is of the form : PFT65871<;>4
|
// The JMRI clock command is of the form : PFT65871<;>4
|
||||||
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
|
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
|
||||||
|
@ -181,7 +186,20 @@ void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte
|
||||||
case 1:
|
case 1:
|
||||||
if (clocktime != lastclocktime){
|
if (clocktime != lastclocktime){
|
||||||
// CAH. DIAG removed because LCD does it anyway.
|
// CAH. DIAG removed because LCD does it anyway.
|
||||||
|
|
||||||
|
#ifndef FASTCLOCK_READABLE
|
||||||
LCD(6,F("Clk Time: %d Sp %d"), clocktime, clockrate);
|
LCD(6,F("Clk Time: %d Sp %d"), clocktime, clockrate);
|
||||||
|
#else
|
||||||
|
// Make Time readable
|
||||||
|
int hours = clocktime / 60;
|
||||||
|
int minutes = clocktime - (hours * 60);
|
||||||
|
int hoursH = hours / 10;
|
||||||
|
int hoursL = hours - (hoursH * 10);
|
||||||
|
int minutesH = minutes / 10;
|
||||||
|
int minutesL = minutes - (minutesH * 10);
|
||||||
|
LCD(6,F("Clk Time: %d%d:%d%d Sp %d"), hoursH, hoursL, minutesH, minutesL, clockrate);
|
||||||
|
#endif
|
||||||
|
|
||||||
// look for an event for this time
|
// look for an event for this time
|
||||||
RMFT2::clockEvent(clocktime,1);
|
RMFT2::clockEvent(clocktime,1);
|
||||||
// Now tell everyone else what the time is.
|
// Now tell everyone else what the time is.
|
||||||
|
@ -244,27 +262,128 @@ void CommandDistributor::broadcastLoco(byte slot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandDistributor::broadcastPower() {
|
void CommandDistributor::broadcastPower() {
|
||||||
|
char pstr[] = "? x";
|
||||||
|
for(byte t=0; t<TrackManager::MAX_TRACKS; t++)
|
||||||
|
if (TrackManager::getPower(t, pstr))
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<p%s>\n"),pstr);
|
||||||
|
|
||||||
|
byte trackcount=0;
|
||||||
|
byte oncount=0;
|
||||||
|
byte offcount=0;
|
||||||
|
for(byte t=0; t<TrackManager::MAX_TRACKS; t++) {
|
||||||
|
if (TrackManager::isActive(t)) {
|
||||||
|
trackcount++;
|
||||||
|
// do not call getPower(t) unless isActive(t)!
|
||||||
|
if (TrackManager::getPower(t) == POWERMODE::ON)
|
||||||
|
oncount++;
|
||||||
|
else
|
||||||
|
offcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//DIAG(F("t=%d on=%d off=%d"), trackcount, oncount, offcount);
|
||||||
|
|
||||||
|
char state='2';
|
||||||
|
if (oncount==0 || offcount == trackcount)
|
||||||
|
state = '0';
|
||||||
|
else if (oncount == trackcount) {
|
||||||
|
state = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
// additional info about MAIN, PROG and JOIN
|
||||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||||
bool join=TrackManager::isJoined();
|
bool join=TrackManager::isJoined();
|
||||||
|
//DIAG(F("m=%d p=%d j=%d"), main, prog, join);
|
||||||
const FSH * reason=F("");
|
const FSH * reason=F("");
|
||||||
char state='1';
|
if (join) {
|
||||||
if (main && prog && join) reason=F(" JOIN");
|
reason = F(" JOIN"); // with space at start so we can append without space
|
||||||
else if (main && prog);
|
broadcastReply(COMMAND_TYPE, F("<p1 %S>\n"),reason);
|
||||||
else if (main) reason=F(" MAIN");
|
} else {
|
||||||
else if (prog) reason=F(" PROG");
|
if (main) {
|
||||||
else state='0';
|
//reason = F("MAIN");
|
||||||
broadcastReply(COMMAND_TYPE, F("<p%c%S>\n"),state,reason);
|
broadcastReply(COMMAND_TYPE, F("<p1 MAIN>\n"));
|
||||||
|
}
|
||||||
|
if (prog) {
|
||||||
|
//reason = F("PROG");
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<p1 PROG>\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state != '2')
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<p%c>\n"),state);
|
||||||
#ifdef CD_HANDLE_RING
|
#ifdef CD_HANDLE_RING
|
||||||
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1':'0');
|
// send '1' if all main are on, otherwise global state (which in that case is '0' or '2')
|
||||||
|
broadcastReply(WITHROTTLE_TYPE, F("PPA%c\n"), main?'1': state);
|
||||||
#endif
|
#endif
|
||||||
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
|
|
||||||
|
LCD(2,F("Power %S%S"),state=='1'?F("On"): ( state=='0'? F("Off") : F("SC") ),reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||||
broadcastReply(type, F("%s"),msg);
|
broadcastReply(type, F("%s"),msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
|
void CommandDistributor::broadcastMessage(char * message) {
|
||||||
broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) {
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<jB %d %d>\n"),routeId,state);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) {
|
||||||
|
broadcastReply(COMMAND_TYPE, F("<jB %d \"%S\">\n"),routeId,caption);
|
||||||
|
}
|
||||||
|
|
||||||
|
Print * CommandDistributor::getVirtualLCDSerial(byte screen, byte row) {
|
||||||
|
Print * stream=virtualLCDSerial;
|
||||||
|
#ifdef CD_HANDLE_RING
|
||||||
|
rememberVLCDClient=RingStream::NO_CLIENT;
|
||||||
|
if (!stream && virtualLCDClient!=RingStream::NO_CLIENT) {
|
||||||
|
// If we are broadcasting from a wifi/eth process we need to complete its output
|
||||||
|
// before merging broadcasts in the ring, then reinstate it in case
|
||||||
|
// the process continues to output to its client.
|
||||||
|
if ((rememberVLCDClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
|
||||||
|
ring->commit();
|
||||||
|
}
|
||||||
|
ring->mark(virtualLCDClient);
|
||||||
|
stream=ring;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (stream) StringFormatter::send(stream,F("<@ %d %d \""), screen,row);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::commitVirtualLCDSerial() {
|
||||||
|
#ifdef CD_HANDLE_RING
|
||||||
|
if (virtualLCDClient!=RingStream::NO_CLIENT) {
|
||||||
|
StringFormatter::send(ring,F("\">\n"));
|
||||||
|
ring->commit();
|
||||||
|
if (rememberVLCDClient!=RingStream::NO_CLIENT) ring->mark(rememberVLCDClient);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
StringFormatter::send(virtualLCDSerial,F("\">\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandDistributor::setVirtualLCDSerial(Print * stream) {
|
||||||
|
#ifdef CD_HANDLE_RING
|
||||||
|
virtualLCDClient=RingStream::NO_CLIENT;
|
||||||
|
if (stream && stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) {
|
||||||
|
virtualLCDClient=((RingStream *) stream)->peekTargetMark();
|
||||||
|
virtualLCDSerial=nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
virtualLCDSerial=stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
Print* CommandDistributor::virtualLCDSerial=&USB_SERIAL;
|
||||||
|
byte CommandDistributor::virtualLCDClient=0xFF;
|
||||||
|
byte CommandDistributor::rememberVLCDClient=0;
|
||||||
|
|
||||||
|
|
|
@ -49,15 +49,27 @@ public :
|
||||||
static void broadcastLoco(byte slot);
|
static void broadcastLoco(byte slot);
|
||||||
static void broadcastSensor(int16_t id, bool value);
|
static void broadcastSensor(int16_t id, bool value);
|
||||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||||
|
static void broadcastTurntable(int16_t id, uint8_t position, bool moving);
|
||||||
static void broadcastClockTime(int16_t time, int8_t rate);
|
static void broadcastClockTime(int16_t time, int8_t rate);
|
||||||
static void setClockTime(int16_t time, int8_t rate, byte opt);
|
static void setClockTime(int16_t time, int8_t rate, byte opt);
|
||||||
static int16_t retClockTime();
|
static int16_t retClockTime();
|
||||||
static void broadcastPower();
|
static void broadcastPower();
|
||||||
static void broadcastRaw(clientType type,char * msg);
|
static void broadcastRaw(clientType type,char * msg);
|
||||||
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
|
static void broadcastTrackState(const FSH* format,byte trackLetter, const FSH* modename, int16_t dcAddr);
|
||||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||||
static void forget(byte clientId);
|
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);
|
||||||
|
static void commitVirtualLCDSerial();
|
||||||
|
static void setVirtualLCDSerial(Print * stream);
|
||||||
|
private:
|
||||||
|
static Print * virtualLCDSerial;
|
||||||
|
static byte virtualLCDClient;
|
||||||
|
static byte rememberVLCDClient;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -65,6 +65,9 @@
|
||||||
#ifdef EXRAIL_WARNING
|
#ifdef EXRAIL_WARNING
|
||||||
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
|
#warning You have myAutomation.h but your hardware has not enough memory to do that, so EX-RAIL DISABLED
|
||||||
#endif
|
#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()
|
void setup()
|
||||||
{
|
{
|
||||||
|
@ -76,6 +79,12 @@ void setup()
|
||||||
|
|
||||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||||
|
|
||||||
|
// If user has defined a startup delay, delay here before starting IO
|
||||||
|
#if defined(STARTUP_DELAY)
|
||||||
|
DIAG(F("Delaying startup for %dms"), STARTUP_DELAY);
|
||||||
|
delay(STARTUP_DELAY);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||||
IODevice::begin();
|
IODevice::begin();
|
||||||
|
|
||||||
|
@ -87,7 +96,7 @@ void setup()
|
||||||
|
|
||||||
DISPLAY_START (
|
DISPLAY_START (
|
||||||
// This block is still executed for DIAGS if display not in use
|
// This block is still executed for DIAGS if display not in use
|
||||||
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
LCD(0,F("DCC-EX v" VERSION));
|
||||||
LCD(1,F("Lic GPLv3"));
|
LCD(1,F("Lic GPLv3"));
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -96,10 +105,12 @@ void setup()
|
||||||
// Start Ethernet if it exists
|
// Start Ethernet if it exists
|
||||||
#ifndef ARDUINO_ARCH_ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
#if WIFI_ON
|
#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);
|
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
|
#endif // WIFI_ON
|
||||||
#else
|
#else
|
||||||
// ESP32 needs wifi on always
|
// 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);
|
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||||
#endif // ARDUINO_ARCH_ESP32
|
#endif // ARDUINO_ARCH_ESP32
|
||||||
|
|
||||||
|
|
198
DCC.cpp
198
DCC.cpp
|
@ -122,7 +122,7 @@ void DCC::setThrottle2( uint16_t cab, byte speedCode) {
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
void DCC::setFunctionInternal(int cab, byte byte1, byte byte2, byte count) {
|
||||||
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
// DIAG(F("setFunctionInternal %d %x %x"),cab,byte1,byte2);
|
||||||
byte b[4];
|
byte b[4];
|
||||||
byte nB = 0;
|
byte nB = 0;
|
||||||
|
@ -133,7 +133,7 @@ void DCC::setFunctionInternal(int cab, byte byte1, byte byte2) {
|
||||||
if (byte1!=0) b[nB++] = byte1;
|
if (byte1!=0) b[nB++] = byte1;
|
||||||
b[nB++] = byte2;
|
b[nB++] = byte2;
|
||||||
|
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 0);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns speed steps 0 to 127 (1 == emergency stop)
|
// returns speed steps 0 to 127 (1 == emergency stop)
|
||||||
|
@ -153,6 +153,22 @@ uint8_t DCC::getThrottleSpeedByte(int cab) {
|
||||||
return speedTable[reg].speedCode;
|
return speedTable[reg].speedCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns 0 to 7 for frequency
|
||||||
|
uint8_t DCC::getThrottleFrequency(int cab) {
|
||||||
|
#if defined(ARDUINO_AVR_UNO)
|
||||||
|
(void)cab;
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
|
int reg=lookupSpeedTable(cab);
|
||||||
|
if (reg<0)
|
||||||
|
return 0; // use default frequency
|
||||||
|
// shift out first 29 bits so we have the 3 "frequency bits" left
|
||||||
|
uint8_t res = (uint8_t)(speedTable[reg].functions >>29);
|
||||||
|
//DIAG(F("Speed table %d functions %l shifted %d"), reg, speedTable[reg].functions, res);
|
||||||
|
return res;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
// returns direction on loco
|
// returns direction on loco
|
||||||
// or true/forward on "loco not found"
|
// or true/forward on "loco not found"
|
||||||
bool DCC::getThrottleDirection(int cab) {
|
bool DCC::getThrottleDirection(int cab) {
|
||||||
|
@ -183,43 +199,55 @@ bool DCC::setFn( int cab, int16_t functionNumber, bool on) {
|
||||||
b[nB++] = functionNumber >>7 ; // high order bits
|
b[nB++] = functionNumber >>7 ; // high order bits
|
||||||
}
|
}
|
||||||
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
DCCWaveform::mainTrack.schedulePacket(b, nB, 4);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
// We use the reminder table up to 28 for normal functions.
|
||||||
|
// We use 29 to 31 for DC frequency as well so up to 28
|
||||||
|
// are "real" functions and 29 to 31 are frequency bits
|
||||||
|
// controlled by function buttons
|
||||||
|
if (functionNumber > 31)
|
||||||
|
return true;
|
||||||
|
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return false;
|
if (reg<0) return false;
|
||||||
|
|
||||||
// Take care of functions:
|
// Take care of functions:
|
||||||
// Set state of function
|
// Set state of function
|
||||||
unsigned long previous=speedTable[reg].functions;
|
uint32_t previous=speedTable[reg].functions;
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
uint32_t funcmask = (1UL<<functionNumber);
|
||||||
if (on) {
|
if (on) {
|
||||||
speedTable[reg].functions |= funcmask;
|
speedTable[reg].functions |= funcmask;
|
||||||
} else {
|
} else {
|
||||||
speedTable[reg].functions &= ~funcmask;
|
speedTable[reg].functions &= ~funcmask;
|
||||||
}
|
}
|
||||||
if (speedTable[reg].functions != previous) {
|
if (speedTable[reg].functions != previous) {
|
||||||
|
if (functionNumber <= 28)
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
CommandDistributor::broadcastLoco(reg);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flip function state
|
// Flip function state (used from withrottle protocol)
|
||||||
void DCC::changeFn( int cab, int16_t functionNumber) {
|
void DCC::changeFn( int cab, int16_t functionNumber) {
|
||||||
if (cab<=0 || functionNumber>28) return;
|
if (cab<=0 || functionNumber>31) return;
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return;
|
if (reg<0) return;
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
speedTable[reg].functions ^= funcmask;
|
speedTable[reg].functions ^= funcmask;
|
||||||
|
if (functionNumber <= 28) {
|
||||||
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
updateGroupflags(speedTable[reg].groupFlags, functionNumber);
|
||||||
|
}
|
||||||
CommandDistributor::broadcastLoco(reg);
|
CommandDistributor::broadcastLoco(reg);
|
||||||
}
|
}
|
||||||
|
|
||||||
int DCC::getFn( int cab, int16_t functionNumber) {
|
// Report function state (used from withrottle protocol)
|
||||||
if (cab<=0 || functionNumber>28) return -1; // unknown
|
// returns 0 false, 1 true or -1 for do not know
|
||||||
|
int8_t DCC::getFn( int cab, int16_t functionNumber) {
|
||||||
|
if (cab<=0 || functionNumber>31)
|
||||||
|
return -1; // unknown
|
||||||
int reg = lookupSpeedTable(cab);
|
int reg = lookupSpeedTable(cab);
|
||||||
if (reg<0) return -1;
|
if (reg<0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
unsigned long funcmask = (1UL<<functionNumber);
|
unsigned long funcmask = (1UL<<functionNumber);
|
||||||
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
return (speedTable[reg].functions & funcmask)? 1 : 0;
|
||||||
|
@ -278,6 +306,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<EFBFBD>r den ersten erweiterten Zubeh<EFBFBD>rdecoder ist wie bei den einfachen
|
||||||
|
Zubeh<EFBFBD>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
|
// writeCVByteMain: Write a byte with PoM on main. This writes
|
||||||
// the 5 byte sized packet to implement this DCC function
|
// the 5 byte sized packet to implement this DCC function
|
||||||
|
@ -421,6 +500,36 @@ const ackOp FLASH READ_CV_PROG[] = {
|
||||||
|
|
||||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
|
// first check cv20 for extended addressing
|
||||||
|
SETCV, (ackOp)20, // CV 19 is extended
|
||||||
|
SETBYTE, (ackOp)0,
|
||||||
|
VB, WACK, ITSKIP, // skip past extended section if cv20 is zero
|
||||||
|
// read cv20 and 19 and merge
|
||||||
|
STARTMERGE, // Setup to read cv 20
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
VB, WACK, NAKSKIP, // bad read of cv20, assume its 0
|
||||||
|
STASHLOCOID, // keep cv 20 until we have cv19 as well.
|
||||||
|
SETCV, (ackOp)19,
|
||||||
|
STARTMERGE, // Setup to read cv 19
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
V0, WACK, MERGE,
|
||||||
|
VB, WACK, NAKFAIL, // cant recover if cv 19 unreadable
|
||||||
|
COMBINE1920, // Combile byte with stash and callback
|
||||||
|
// end of advanced 20,19 check
|
||||||
|
SKIPTARGET,
|
||||||
SETCV, (ackOp)19, // CV 19 is consist setting
|
SETCV, (ackOp)19, // CV 19 is consist setting
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
VB, WACK, ITSKIP, // ignore consist if cv19 is zero (no consist)
|
||||||
|
@ -487,6 +596,10 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
||||||
|
|
||||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
|
// Clear consist CV 19,20
|
||||||
|
SETCV,(ackOp)20,
|
||||||
|
SETBYTE, (ackOp)0,
|
||||||
|
WB,WACK, // ignore dedcoder without cv20 support
|
||||||
SETCV,(ackOp)19,
|
SETCV,(ackOp)19,
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
WB,WACK, // ignore dedcoder without cv19 support
|
WB,WACK, // ignore dedcoder without cv19 support
|
||||||
|
@ -502,9 +615,25 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||||
CALLFAIL
|
CALLFAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// for CONSIST_ID_PROG the 20,19 values are already calculated
|
||||||
|
const ackOp FLASH CONSIST_ID_PROG[] = {
|
||||||
|
BASELINE,
|
||||||
|
SETCV,(ackOp)20,
|
||||||
|
SETBYTEH, // high byte to CV 20
|
||||||
|
WB,WACK, // ignore dedcoder without cv20 support
|
||||||
|
SETCV,(ackOp)19,
|
||||||
|
SETBYTEL, // low byte of word
|
||||||
|
WB,WACK,ITC1, // If ACK, we are done - callback(1) means Ok
|
||||||
|
VB,WACK,ITC1, // Some decoders do not ack and need verify
|
||||||
|
CALLFAIL
|
||||||
|
};
|
||||||
|
|
||||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
// Clear consist CV 19
|
// Clear consist CV 19,20
|
||||||
|
SETCV,(ackOp)20,
|
||||||
|
SETBYTE, (ackOp)0,
|
||||||
|
WB,WACK, // ignore dedcoder without cv20 support
|
||||||
SETCV,(ackOp)19,
|
SETCV,(ackOp)19,
|
||||||
SETBYTE, (ackOp)0,
|
SETBYTE, (ackOp)0,
|
||||||
WB,WACK, // ignore decoder without cv19 support
|
WB,WACK, // ignore decoder without cv19 support
|
||||||
|
@ -573,6 +702,26 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||||
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
DCCACK::Setup(id | 0xc000,LONG_LOCO_ID_PROG, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCC::setConsistId(int id,bool reverse,ACK_CALLBACK callback) {
|
||||||
|
if (id<0 || id>10239) { //0x27FF according to standard
|
||||||
|
callback(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte cv20;
|
||||||
|
byte cv19;
|
||||||
|
|
||||||
|
if (id<=HIGHEST_SHORT_ADDR) {
|
||||||
|
cv19=id;
|
||||||
|
cv20=0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cv20=id/100;
|
||||||
|
cv19=id%100;
|
||||||
|
}
|
||||||
|
if (reverse) cv19|=0x80;
|
||||||
|
DCCACK::Setup((cv20<<8)|cv19, CONSIST_ID_PROG, callback);
|
||||||
|
}
|
||||||
|
|
||||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||||
setThrottle2(cab,1); // ESTOP this loco if still on track
|
setThrottle2(cab,1); // ESTOP this loco if still on track
|
||||||
int reg=lookupSpeedTable(cab, false);
|
int reg=lookupSpeedTable(cab, false);
|
||||||
|
@ -595,7 +744,7 @@ void DCC::loop() {
|
||||||
|
|
||||||
void DCC::issueReminders() {
|
void DCC::issueReminders() {
|
||||||
// if the main track transmitter still has a pending packet, skip this time around.
|
// if the main track transmitter still has a pending packet, skip this time around.
|
||||||
if ( DCCWaveform::mainTrack.getPacketPending()) return;
|
if (!DCCWaveform::mainTrack.isReminderWindowOpen()) return;
|
||||||
// Move to next loco slot. If occupied, send a reminder.
|
// Move to next loco slot. If occupied, send a reminder.
|
||||||
int reg = lastLocoReminder+1;
|
int reg = lastLocoReminder+1;
|
||||||
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
if (reg > highestUsedReg) reg = 0; // Go to start of table
|
||||||
|
@ -619,24 +768,39 @@ bool DCC::issueReminder(int reg) {
|
||||||
break;
|
break;
|
||||||
case 1: // remind function group 1 (F0-F4)
|
case 1: // remind function group 1 (F0-F4)
|
||||||
if (flags & FN_GROUP_1)
|
if (flags & FN_GROUP_1)
|
||||||
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4)); // 100D DDDD
|
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||||
|
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),0); // 100D DDDD
|
||||||
|
#else
|
||||||
|
setFunctionInternal(loco,0, 128 | ((functions>>1)& 0x0F) | ((functions & 0x01)<<4),2);
|
||||||
|
flags&= ~FN_GROUP_1; // dont send them again
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case 2: // remind function group 2 F5-F8
|
case 2: // remind function group 2 F5-F8
|
||||||
if (flags & FN_GROUP_2)
|
if (flags & FN_GROUP_2)
|
||||||
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F)); // 1011 DDDD
|
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||||
|
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),0); // 1011 DDDD
|
||||||
|
#else
|
||||||
|
setFunctionInternal(loco,0, 176 | ((functions>>5)& 0x0F),2);
|
||||||
|
flags&= ~FN_GROUP_2; // dont send them again
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case 3: // remind function group 3 F9-F12
|
case 3: // remind function group 3 F9-F12
|
||||||
if (flags & FN_GROUP_3)
|
if (flags & FN_GROUP_3)
|
||||||
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F)); // 1010 DDDD
|
#ifndef DISABLE_FUNCTION_REMINDERS
|
||||||
|
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),0); // 1010 DDDD
|
||||||
|
#else
|
||||||
|
setFunctionInternal(loco,0, 160 | ((functions>>9)& 0x0F),2);
|
||||||
|
flags&= ~FN_GROUP_3; // dont send them again
|
||||||
|
#endif
|
||||||
break;
|
break;
|
||||||
case 4: // remind function group 4 F13-F20
|
case 4: // remind function group 4 F13-F20
|
||||||
if (flags & FN_GROUP_4)
|
if (flags & FN_GROUP_4)
|
||||||
setFunctionInternal(loco,222, ((functions>>13)& 0xFF));
|
setFunctionInternal(loco,222, ((functions>>13)& 0xFF),2);
|
||||||
flags&= ~FN_GROUP_4; // dont send them again
|
flags&= ~FN_GROUP_4; // dont send them again
|
||||||
break;
|
break;
|
||||||
case 5: // remind function group 5 F21-F28
|
case 5: // remind function group 5 F21-F28
|
||||||
if (flags & FN_GROUP_5)
|
if (flags & FN_GROUP_5)
|
||||||
setFunctionInternal(loco,223, ((functions>>21)& 0xFF));
|
setFunctionInternal(loco,223, ((functions>>21)& 0xFF),2);
|
||||||
flags&= ~FN_GROUP_5; // dont send them again
|
flags&= ~FN_GROUP_5; // dont send them again
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
10
DCC.h
10
DCC.h
|
@ -61,16 +61,18 @@ public:
|
||||||
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection);
|
||||||
static int8_t getThrottleSpeed(int cab);
|
static int8_t getThrottleSpeed(int cab);
|
||||||
static uint8_t getThrottleSpeedByte(int cab);
|
static uint8_t getThrottleSpeedByte(int cab);
|
||||||
|
static uint8_t getThrottleFrequency(int cab);
|
||||||
static bool getThrottleDirection(int cab);
|
static bool getThrottleDirection(int cab);
|
||||||
static void writeCVByteMain(int cab, int cv, byte bValue);
|
static void writeCVByteMain(int cab, int cv, byte bValue);
|
||||||
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue);
|
||||||
static void setFunction(int cab, byte fByte, byte eByte);
|
static void setFunction(int cab, byte fByte, byte eByte);
|
||||||
static bool setFn(int cab, int16_t functionNumber, bool on);
|
static bool setFn(int cab, int16_t functionNumber, bool on);
|
||||||
static void changeFn(int cab, int16_t functionNumber);
|
static void changeFn(int cab, int16_t functionNumber);
|
||||||
static int getFn(int cab, int16_t functionNumber);
|
static int8_t getFn(int cab, int16_t functionNumber);
|
||||||
static uint32_t getFunctionMap(int cab);
|
static uint32_t getFunctionMap(int cab);
|
||||||
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
static void updateGroupflags(byte &flags, int16_t functionNumber);
|
||||||
static void setAccessory(int address, byte port, bool gate, byte onoff = 2);
|
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);
|
static bool writeTextPacket(byte *b, int nBytes);
|
||||||
|
|
||||||
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
// ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1
|
||||||
|
@ -83,7 +85,7 @@ public:
|
||||||
|
|
||||||
static void getLocoId(ACK_CALLBACK callback);
|
static void getLocoId(ACK_CALLBACK callback);
|
||||||
static void setLocoId(int id,ACK_CALLBACK callback);
|
static void setLocoId(int id,ACK_CALLBACK callback);
|
||||||
|
static void setConsistId(int id,bool reverse,ACK_CALLBACK callback);
|
||||||
// Enhanced API functions
|
// Enhanced API functions
|
||||||
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
static void forgetLoco(int cab); // removes any speed reminders for this loco
|
||||||
static void forgetAllLocos(); // removes all speed reminders
|
static void forgetAllLocos(); // removes all speed reminders
|
||||||
|
@ -98,7 +100,7 @@ public:
|
||||||
int loco;
|
int loco;
|
||||||
byte speedCode;
|
byte speedCode;
|
||||||
byte groupFlags;
|
byte groupFlags;
|
||||||
unsigned long functions;
|
uint32_t functions;
|
||||||
};
|
};
|
||||||
static LOCO speedTable[MAX_LOCOS];
|
static LOCO speedTable[MAX_LOCOS];
|
||||||
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
static int lookupSpeedTable(int locoId, bool autoCreate=true);
|
||||||
|
@ -109,7 +111,7 @@ private:
|
||||||
static byte loopStatus;
|
static byte loopStatus;
|
||||||
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
static void setThrottle2(uint16_t cab, uint8_t speedCode);
|
||||||
static void updateLocoReminder(int loco, byte speedCode);
|
static void updateLocoReminder(int loco, byte speedCode);
|
||||||
static void setFunctionInternal(int cab, byte fByte, byte eByte);
|
static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count);
|
||||||
static bool issueReminder(int reg);
|
static bool issueReminder(int reg);
|
||||||
static int lastLocoReminder;
|
static int lastLocoReminder;
|
||||||
static int highestUsedReg;
|
static int highestUsedReg;
|
||||||
|
|
17
DCCACK.cpp
17
DCCACK.cpp
|
@ -314,6 +314,14 @@ void DCCACK::loop() {
|
||||||
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
callback( LONG_ADDR_MARKER | ( ackManagerByte + ((ackManagerStash - 192) << 8)));
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
case COMBINE1920:
|
||||||
|
// ackManagerStash is cv20, ackManagerByte is CV 19
|
||||||
|
// This will not be called if cv20==0
|
||||||
|
ackManagerByte &= 0x7F; // ignore direction marker
|
||||||
|
ackManagerByte %=100; // take last 2 decimal digits
|
||||||
|
callback( ackManagerStash*100+ackManagerByte);
|
||||||
|
return;
|
||||||
|
|
||||||
case ITSKIP:
|
case ITSKIP:
|
||||||
if (!ackReceived) break;
|
if (!ackReceived) break;
|
||||||
// SKIP opcodes until SKIPTARGET found
|
// SKIP opcodes until SKIPTARGET found
|
||||||
|
@ -322,6 +330,15 @@ void DCCACK::loop() {
|
||||||
opcode=GETFLASH(ackManagerProg);
|
opcode=GETFLASH(ackManagerProg);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case NAKSKIP:
|
||||||
|
if (ackReceived) break;
|
||||||
|
// SKIP opcodes until SKIPTARGET found
|
||||||
|
while (opcode!=SKIPTARGET) {
|
||||||
|
ackManagerProg++;
|
||||||
|
opcode=GETFLASH(ackManagerProg);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case SKIPTARGET:
|
case SKIPTARGET:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
2
DCCACK.h
2
DCCACK.h
|
@ -56,6 +56,8 @@ enum ackOp : byte
|
||||||
STASHLOCOID, // keeps current byte value for later
|
STASHLOCOID, // keeps current byte value for later
|
||||||
COMBINELOCOID, // combines current value with stashed value and returns it
|
COMBINELOCOID, // combines current value with stashed value and returns it
|
||||||
ITSKIP, // skip to SKIPTARGET if ack true
|
ITSKIP, // skip to SKIPTARGET if ack true
|
||||||
|
NAKSKIP, // skip to SKIPTARGET if ack false
|
||||||
|
COMBINE1920, // combine cvs 19 and 20 and callback
|
||||||
SKIPTARGET = 0xFF // jump to target
|
SKIPTARGET = 0xFF // jump to target
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
1
DCCEX.h
1
DCCEX.h
|
@ -49,6 +49,7 @@
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
|
#include "KeywordHasher.h"
|
||||||
#include "EXRAIL.h"
|
#include "EXRAIL.h"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
643
DCCEXParser.cpp
643
DCCEXParser.cpp
|
@ -45,11 +45,11 @@ Once a new OPCODE is decided upon, update this list.
|
||||||
0, Track power off
|
0, Track power off
|
||||||
1, Track power on
|
1, Track power on
|
||||||
a, DCC accessory control
|
a, DCC accessory control
|
||||||
A,
|
A, DCC extended accessory control
|
||||||
b, Write CV bit on main
|
b, Write CV bit on main
|
||||||
B, Write CV bit
|
B, Write CV bit
|
||||||
c, Request current command
|
c, Request current command
|
||||||
C,
|
C, configure the CS
|
||||||
d,
|
d,
|
||||||
D, Diagnostic commands
|
D, Diagnostic commands
|
||||||
e, Erase EEPROM
|
e, Erase EEPROM
|
||||||
|
@ -60,18 +60,18 @@ Once a new OPCODE is decided upon, update this list.
|
||||||
G,
|
G,
|
||||||
h,
|
h,
|
||||||
H, Turnout state broadcast
|
H, Turnout state broadcast
|
||||||
i, Reserved for future use - Turntable object broadcast
|
i, Server details string
|
||||||
I, Reserved for future use - Turntable object command and control
|
I, Turntable object command, control, and broadcast
|
||||||
j, Throttle responses
|
j, Throttle responses
|
||||||
J, Throttle queries
|
J, Throttle queries
|
||||||
k, Reserved for future use - Potentially Railcom
|
k, Reserved for future use - Potentially Railcom
|
||||||
K, Reserved for future use - Potentially Railcom
|
K, Reserved for future use - Potentially Railcom
|
||||||
l, Loco speedbyte/function map broadcast
|
l, Loco speedbyte/function map broadcast
|
||||||
L,
|
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||||
m,
|
m, message to throttles broadcast
|
||||||
M, Write DCC packet
|
M, Write DCC packet
|
||||||
n,
|
n, Reserved for SensorCam
|
||||||
N,
|
N, Reserved for Sensorcam
|
||||||
o,
|
o,
|
||||||
O, Output broadcast
|
O, Output broadcast
|
||||||
p, Broadcast power state
|
p, Broadcast power state
|
||||||
|
@ -91,10 +91,10 @@ Once a new OPCODE is decided upon, update this list.
|
||||||
w, Write CV on main
|
w, Write CV on main
|
||||||
W, Write CV
|
W, Write CV
|
||||||
x,
|
x,
|
||||||
X, Invalid command
|
X, Invalid command response
|
||||||
y,
|
y,
|
||||||
Y, Output broadcast
|
Y, Output broadcast
|
||||||
z,
|
z, Direct output
|
||||||
Z, Output configuration/control
|
Z, Output configuration/control
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -114,6 +114,9 @@ Once a new OPCODE is decided upon, update this list.
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "EXRAIL2.h"
|
#include "EXRAIL2.h"
|
||||||
|
#include "Turntables.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "KeywordHasher.h"
|
||||||
|
|
||||||
// This macro can't be created easily as a portable function because the
|
// This macro can't be created easily as a portable function because the
|
||||||
// flashlist requires a far pointer for high flash access.
|
// flashlist requires a far pointer for high flash access.
|
||||||
|
@ -124,51 +127,6 @@ Once a new OPCODE is decided upon, update this list.
|
||||||
StringFormatter::send(stream,F(" %d"),value); \
|
StringFormatter::send(stream,F(" %d"),value); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
|
||||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
|
||||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
|
||||||
const int16_t HASH_KEYWORD_CABS = -11981;
|
|
||||||
const int16_t HASH_KEYWORD_RAM = 25982;
|
|
||||||
const int16_t HASH_KEYWORD_CMD = 9962;
|
|
||||||
const int16_t HASH_KEYWORD_ACK = 3113;
|
|
||||||
const int16_t HASH_KEYWORD_ON = 2657;
|
|
||||||
const int16_t HASH_KEYWORD_DCC = 6436;
|
|
||||||
const int16_t HASH_KEYWORD_SLOW = -17209;
|
|
||||||
#ifndef DISABLE_PROG
|
|
||||||
const int16_t HASH_KEYWORD_JOIN = -30750;
|
|
||||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
|
||||||
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
|
|
||||||
#endif
|
|
||||||
#ifndef DISABLE_EEPROM
|
|
||||||
const int16_t HASH_KEYWORD_EEPROM = -7168;
|
|
||||||
#endif
|
|
||||||
const int16_t HASH_KEYWORD_LIMIT = 27413;
|
|
||||||
const int16_t HASH_KEYWORD_MAX = 16244;
|
|
||||||
const int16_t HASH_KEYWORD_MIN = 15978;
|
|
||||||
const int16_t HASH_KEYWORD_RESET = 26133;
|
|
||||||
const int16_t HASH_KEYWORD_RETRY = 25704;
|
|
||||||
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
|
||||||
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
|
||||||
const int16_t HASH_KEYWORD_SERVO=27709;
|
|
||||||
const int16_t HASH_KEYWORD_TT=2688;
|
|
||||||
const int16_t HASH_KEYWORD_VPIN=-415;
|
|
||||||
const int16_t HASH_KEYWORD_A='A';
|
|
||||||
const int16_t HASH_KEYWORD_C='C';
|
|
||||||
const int16_t HASH_KEYWORD_G='G';
|
|
||||||
const int16_t HASH_KEYWORD_I='I';
|
|
||||||
const int16_t HASH_KEYWORD_R='R';
|
|
||||||
const int16_t HASH_KEYWORD_T='T';
|
|
||||||
const int16_t HASH_KEYWORD_X='X';
|
|
||||||
const int16_t HASH_KEYWORD_LCN = 15137;
|
|
||||||
const int16_t HASH_KEYWORD_HAL = 10853;
|
|
||||||
const int16_t HASH_KEYWORD_SHOW = -21309;
|
|
||||||
const int16_t HASH_KEYWORD_ANIN = -10424;
|
|
||||||
const int16_t HASH_KEYWORD_ANOUT = -26399;
|
|
||||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
|
||||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
|
||||||
const int16_t HASH_KEYWORD_WIT = 31594;
|
|
||||||
|
|
||||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||||
bool DCCEXParser::stashBusy;
|
bool DCCEXParser::stashBusy;
|
||||||
Print *DCCEXParser::stashStream = NULL;
|
Print *DCCEXParser::stashStream = NULL;
|
||||||
|
@ -204,7 +162,9 @@ int16_t DCCEXParser::splitValues(int16_t result[MAX_COMMAND_PARAMS], const byte
|
||||||
case 1: // skipping spaces before a param
|
case 1: // skipping spaces before a param
|
||||||
if (hot == ' ')
|
if (hot == ' ')
|
||||||
break;
|
break;
|
||||||
if (hot == '\0' || hot == '>')
|
if (hot == '\0')
|
||||||
|
return -1;
|
||||||
|
if (hot == '>')
|
||||||
return parameterCount;
|
return parameterCount;
|
||||||
state = 2;
|
state = 2;
|
||||||
continue;
|
continue;
|
||||||
|
@ -298,13 +258,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
#ifndef DISABLE_EEPROM
|
#ifndef DISABLE_EEPROM
|
||||||
(void)EEPROM; // tell compiler not to warn this is unused
|
(void)EEPROM; // tell compiler not to warn this is unused
|
||||||
#endif
|
#endif
|
||||||
|
byte params = 0;
|
||||||
if (Diag::CMD)
|
if (Diag::CMD)
|
||||||
DIAG(F("PARSING:%s"), com);
|
DIAG(F("PARSING:%s"), com);
|
||||||
int16_t p[MAX_COMMAND_PARAMS];
|
int16_t p[MAX_COMMAND_PARAMS];
|
||||||
while (com[0] == '<' || com[0] == ' ')
|
while (com[0] == '<' || com[0] == ' ')
|
||||||
com++; // strip off any number of < or spaces
|
com++; // strip off any number of < or spaces
|
||||||
byte opcode = com[0];
|
byte opcode = com[0];
|
||||||
byte params = splitValues(p, com, opcode=='M' || opcode=='P');
|
int16_t splitnum = splitValues(p, com, opcode=='M' || opcode=='P');
|
||||||
|
if (splitnum < 0 || splitnum >= MAX_COMMAND_PARAMS) // if arguments are broken, leave but via printing <X>
|
||||||
|
goto out;
|
||||||
|
// Because of check above we are now inside byte size
|
||||||
|
params = splitnum;
|
||||||
|
|
||||||
if (filterCallback)
|
if (filterCallback)
|
||||||
filterCallback(stream, opcode, params, p);
|
filterCallback(stream, opcode, params, p);
|
||||||
|
@ -318,25 +283,22 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
return; // filterCallback asked us to ignore
|
return; // filterCallback asked us to ignore
|
||||||
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
case 't': // THROTTLE <t [REGISTER] CAB SPEED DIRECTION>
|
||||||
{
|
{
|
||||||
if (params==1) { // <t cab> display state
|
int16_t cab;
|
||||||
|
int16_t tspeed;
|
||||||
|
int16_t direction;
|
||||||
|
|
||||||
|
if (params==1) { // <t cab> display state
|
||||||
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
int16_t slot=DCC::lookupSpeedTable(p[0],false);
|
||||||
if (slot>=0) {
|
if (slot>=0)
|
||||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
CommandDistributor::broadcastLoco(slot);
|
||||||
StringFormatter::send(stream,F("<l %d %d %d %l>\n"),
|
|
||||||
sp->loco,slot,sp->speedCode,sp->functions);
|
|
||||||
}
|
|
||||||
else // send dummy state speed 0 fwd no functions.
|
else // send dummy state speed 0 fwd no functions.
|
||||||
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
StringFormatter::send(stream,F("<l %d -1 128 0>\n"),p[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int16_t cab;
|
|
||||||
int16_t tspeed;
|
|
||||||
int16_t direction;
|
|
||||||
|
|
||||||
if (params == 4)
|
if (params == 4)
|
||||||
{ // <t REGISTER CAB SPEED DIRECTION>
|
{ // <t REGISTER CAB SPEED DIRECTION>
|
||||||
|
// ignore register p[0]
|
||||||
cab = p[1];
|
cab = p[1];
|
||||||
tspeed = p[2];
|
tspeed = p[2];
|
||||||
direction = p[3];
|
direction = p[3];
|
||||||
|
@ -420,6 +382,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
}
|
}
|
||||||
return;
|
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 ...>
|
case 'T': // TURNOUT <T ...>
|
||||||
if (parseT(stream, params, p))
|
if (parseT(stream, params, p))
|
||||||
return;
|
return;
|
||||||
|
@ -451,10 +420,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
|
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
|
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
|
||||||
|
if (params != 3)
|
||||||
|
break;
|
||||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
||||||
|
if (params != 4)
|
||||||
|
break;
|
||||||
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
||||||
return;
|
return;
|
||||||
#endif
|
#endif
|
||||||
|
@ -485,8 +458,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
DCC::setLocoId(p[0],callback_Wloco);
|
DCC::setLocoId(p[0],callback_Wloco);
|
||||||
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||||
DCC::writeCVByte(p[0], p[1], callback_W4);
|
DCC::writeCVByte(p[0], p[1], callback_W4);
|
||||||
else // WRITE CV ON PROG <W CV VALUE>
|
else if ((params==2 || params==3 ) && p[0]=="CONSIST"_hk ) {
|
||||||
|
DCC::setConsistId(p[1],p[2]=="REVERSE"_hk,callback_Wconsist);
|
||||||
|
}
|
||||||
|
else if (params == 2) // WRITE CV ON PROG <W CV VALUE>
|
||||||
DCC::writeCVByte(p[0], p[1], callback_W);
|
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||||
|
else
|
||||||
|
break;
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
||||||
|
@ -506,7 +484,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB> or <B CV BIT VALUE>
|
||||||
|
if (params != 3 && params != 5)
|
||||||
|
break;
|
||||||
if (!stashCallback(stream, p, ringStream))
|
if (!stashCallback(stream, p, ringStream))
|
||||||
break;
|
break;
|
||||||
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
|
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
|
||||||
|
@ -539,67 +519,62 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
|
|
||||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||||
{
|
{
|
||||||
bool main=false;
|
|
||||||
bool prog=false;
|
|
||||||
bool join=false;
|
|
||||||
if (params > 1) break;
|
if (params > 1) break;
|
||||||
if (params==0) { // All
|
if (params==0) { // All
|
||||||
main=true;
|
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
|
||||||
prog=true;
|
|
||||||
}
|
}
|
||||||
if (params==1) {
|
if (params==1) {
|
||||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
if (p[0]=="MAIN"_hk) { // <1 MAIN>
|
||||||
main=true;
|
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
|
||||||
}
|
}
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
else if (p[0] == "JOIN"_hk) { // <1 JOIN>
|
||||||
main=true;
|
TrackManager::setJoin(true);
|
||||||
prog=true;
|
TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
|
||||||
join=true;
|
|
||||||
}
|
}
|
||||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
else if (p[0]=="PROG"_hk) { // <1 PROG>
|
||||||
prog=true;
|
TrackManager::setJoin(false);
|
||||||
|
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
|
||||||
|
byte t = (p[0] - 'A');
|
||||||
|
TrackManager::setTrackPower(POWERMODE::ON, t);
|
||||||
|
//StringFormatter::send(stream, F("<p1 %c>\n"), t+'A');
|
||||||
|
}
|
||||||
else break; // will reply <X>
|
else break; // will reply <X>
|
||||||
}
|
}
|
||||||
TrackManager::setJoin(join);
|
//TrackManager::streamTrackState(NULL,t);
|
||||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
|
||||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
|
||||||
|
|
||||||
CommandDistributor::broadcastPower();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||||
{
|
{
|
||||||
bool main=false;
|
|
||||||
bool prog=false;
|
|
||||||
if (params > 1) break;
|
if (params > 1) break;
|
||||||
if (params==0) { // All
|
if (params==0) { // All
|
||||||
main=true;
|
TrackManager::setJoin(false);
|
||||||
prog=true;
|
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
|
||||||
}
|
}
|
||||||
if (params==1) {
|
if (params==1) {
|
||||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
if (p[0]=="MAIN"_hk) { // <0 MAIN>
|
||||||
main=true;
|
TrackManager::setJoin(false);
|
||||||
|
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
|
||||||
}
|
}
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
else if (p[0]=="PROG"_hk) { // <0 PROG>
|
||||||
prog=true;
|
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||||
|
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
else if (p[0] >= "A"_hk && p[0] <= "H"_hk) { // <1 A-H>
|
||||||
|
byte t = (p[0] - 'A');
|
||||||
|
TrackManager::setJoin(false);
|
||||||
|
TrackManager::setTrackPower(POWERMODE::OFF, t);
|
||||||
|
//StringFormatter::send(stream, F("<p0 %c>\n"), t+'A');
|
||||||
|
}
|
||||||
else break; // will reply <X>
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandDistributor::broadcastPower();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,17 +582,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
case 'c': // SEND METER RESPONSES <c>
|
case 'c': // SEND METER RESPONSES <c>
|
||||||
// No longer useful because of multiple tracks See <JG> and <JI>
|
// No longer useful because of multiple tracks See <JG> and <JI>
|
||||||
if (params>0) break;
|
if (params>0) break;
|
||||||
TrackManager::reportObsoleteCurrent(stream);
|
TrackManager::reportObsoleteCurrent(stream);
|
||||||
return;
|
return;
|
||||||
|
#endif
|
||||||
case 'Q': // SENSORS <Q>
|
case 'Q': // SENSORS <Q>
|
||||||
Sensor::printAll(stream);
|
Sensor::printAll(stream);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case 's': // <s>
|
case 's': // STATUS <s>
|
||||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||||
Turnout::printAll(stream); //send all Turnout states
|
Turnout::printAll(stream); //send all Turnout states
|
||||||
|
@ -638,14 +614,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
case ' ': // < >
|
case ' ': // < >
|
||||||
StringFormatter::send(stream, F("\n"));
|
StringFormatter::send(stream, F("\n"));
|
||||||
return;
|
return;
|
||||||
|
case 'C': // CONFIG <C [params]>
|
||||||
case 'D': // < >
|
if (parseC(stream, params, p))
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
#ifndef DISABLE_DIAG
|
||||||
|
case 'D': // DIAG <D [params]>
|
||||||
if (parseD(stream, params, p))
|
if (parseD(stream, params, p))
|
||||||
return;
|
return;
|
||||||
return;
|
break;
|
||||||
|
#endif
|
||||||
case '=': // <= Track manager control >
|
case '=': // TRACK MANAGER CONTROL <= [params]>
|
||||||
if (TrackManager::parseJ(stream, params, p))
|
if (TrackManager::parseEqualSign(stream, params, p))
|
||||||
return;
|
return;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -682,7 +662,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
//if ((params<1) | (params>2)) break; // <J>
|
//if ((params<1) | (params>2)) break; // <J>
|
||||||
int16_t id=(params==2)?p[1]:0;
|
int16_t id=(params==2)?p[1]:0;
|
||||||
switch(p[0]) {
|
switch(p[0]) {
|
||||||
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
|
case "C"_hk: // <JC mmmm nn> sets time and speed
|
||||||
if (params==1) { // <JC> returns latest time
|
if (params==1) { // <JC> returns latest time
|
||||||
int16_t x = CommandDistributor::retClockTime();
|
int16_t x = CommandDistributor::retClockTime();
|
||||||
StringFormatter::send(stream, F("<jC %d>\n"), x);
|
StringFormatter::send(stream, F("<jC %d>\n"), x);
|
||||||
|
@ -691,38 +671,28 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
CommandDistributor::setClockTime(p[1], p[2], 1);
|
CommandDistributor::setClockTime(p[1], p[2], 1);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case HASH_KEYWORD_G: // <JG> current gauge limits
|
case "G"_hk: // <JG> current gauge limits
|
||||||
if (params>1) break;
|
if (params>1) break;
|
||||||
TrackManager::reportGauges(stream); // <g limit...limit>
|
TrackManager::reportGauges(stream); // <g limit...limit>
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case HASH_KEYWORD_I: // <JI> current values
|
case "I"_hk: // <JI> current values
|
||||||
if (params>1) break;
|
if (params>1) break;
|
||||||
TrackManager::reportCurrent(stream); // <g limit...limit>
|
TrackManager::reportCurrent(stream); // <g limit...limit>
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
case "A"_hk: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
||||||
StringFormatter::send(stream, F("<jA"));
|
if (params!=1) break; // <JA>
|
||||||
if (params==1) {// <JA>
|
StringFormatter::send(stream, F("<jA>\n"));
|
||||||
#ifdef EXRAIL_ACTIVE
|
|
||||||
SENDFLASHLIST(stream,RMFT2::routeIdList)
|
|
||||||
SENDFLASHLIST(stream,RMFT2::automationIdList)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else { // <JA id>
|
|
||||||
StringFormatter::send(stream,F(" %d %c \"%S\""),
|
|
||||||
id,
|
|
||||||
#ifdef EXRAIL_ACTIVE
|
|
||||||
RMFT2::getRouteType(id), // A/R
|
|
||||||
RMFT2::getRouteDescription(id)
|
|
||||||
#else
|
|
||||||
'X',F("")
|
|
||||||
#endif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
StringFormatter::send(stream, F(">\n"));
|
|
||||||
return;
|
return;
|
||||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
|
||||||
|
case "M"_hk: // <JM> intercepted by EXRAIL
|
||||||
|
if (params>1) break; // invalid cant do
|
||||||
|
// <JM> requests stash size so say none.
|
||||||
|
StringFormatter::send(stream,F("<jM 0>\n"));
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "R"_hk: // <JR> returns rosters
|
||||||
StringFormatter::send(stream, F("<jR"));
|
StringFormatter::send(stream, F("<jR"));
|
||||||
#ifdef EXRAIL_ACTIVE
|
#ifdef EXRAIL_ACTIVE
|
||||||
if (params==1) {
|
if (params==1) {
|
||||||
|
@ -741,7 +711,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
#endif
|
#endif
|
||||||
StringFormatter::send(stream, F(">\n"));
|
StringFormatter::send(stream, F(">\n"));
|
||||||
return;
|
return;
|
||||||
case HASH_KEYWORD_T: // <JT> returns turnout list
|
case "T"_hk: // <JT> returns turnout list
|
||||||
StringFormatter::send(stream, F("<jT"));
|
StringFormatter::send(stream, F("<jT"));
|
||||||
if (params==1) { // <JT>
|
if (params==1) { // <JT>
|
||||||
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
|
for ( Turnout * t=Turnout::first(); t; t=t->next()) {
|
||||||
|
@ -766,20 +736,95 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||||
}
|
}
|
||||||
StringFormatter::send(stream, F(">\n"));
|
StringFormatter::send(stream, F(">\n"));
|
||||||
return;
|
return;
|
||||||
|
// No turntables without HAL support
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
case "O"_hk: // <JO returns turntable list
|
||||||
|
StringFormatter::send(stream, F("<jO"));
|
||||||
|
if (params==1) { // <JO>
|
||||||
|
for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) {
|
||||||
|
if (tto->isHidden()) continue;
|
||||||
|
StringFormatter::send(stream, F(" %d"),tto->getId());
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream, F(">\n"));
|
||||||
|
} else { // <JO id>
|
||||||
|
Turntable *tto=Turntable::get(id);
|
||||||
|
if (!tto || tto->isHidden()) {
|
||||||
|
StringFormatter::send(stream, F(" %d X>\n"), id);
|
||||||
|
} else {
|
||||||
|
uint8_t pos = tto->getPosition();
|
||||||
|
uint8_t type = tto->isEXTT();
|
||||||
|
uint8_t posCount = tto->getPositionCount();
|
||||||
|
const FSH *todesc = NULL;
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
todesc = RMFT2::getTurntableDescription(id);
|
||||||
|
#endif
|
||||||
|
if (todesc == NULL) todesc = F("");
|
||||||
|
StringFormatter::send(stream, F(" %d %d %d %d \"%S\">\n"), id, type, pos, posCount, todesc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case "P"_hk: // <JP id> returns turntable position list for the turntable id
|
||||||
|
if (params==2) { // <JP id>
|
||||||
|
Turntable *tto=Turntable::get(id);
|
||||||
|
if (!tto || tto->isHidden()) {
|
||||||
|
StringFormatter::send(stream, F(" %d X>\n"), id);
|
||||||
|
} else {
|
||||||
|
uint8_t posCount = tto->getPositionCount();
|
||||||
|
const FSH *tpdesc = NULL;
|
||||||
|
for (uint8_t p = 0; p < posCount; p++) {
|
||||||
|
StringFormatter::send(stream, F("<jP"));
|
||||||
|
int16_t angle = tto->getPositionAngle(p);
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
|
||||||
|
#endif
|
||||||
|
if (tpdesc == NULL) tpdesc = F("");
|
||||||
|
StringFormatter::send(stream, F(" %d %d %d \"%S\""), id, p, angle, tpdesc);
|
||||||
|
StringFormatter::send(stream, F(">\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
StringFormatter::send(stream, F("<jP X>\n"));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
default: break;
|
default: break;
|
||||||
} // switch(p[1])
|
} // switch(p[1])
|
||||||
break; // case J
|
break; // case J
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No turntables without HAL support
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
case 'I': // TURNTABLE <I ...>
|
||||||
|
if (parseI(stream, params, p))
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case '/': // implemented in EXRAIL parser
|
||||||
|
case 'L': // LCC interface implemented in EXRAIL parser
|
||||||
|
break; // Will <X> if not intercepted by EXRAIL
|
||||||
|
|
||||||
|
#ifndef DISABLE_VDPY
|
||||||
|
case '@': // JMRI saying "give me virtual LCD msgs"
|
||||||
|
CommandDistributor::setVirtualLCDSerial(stream);
|
||||||
|
StringFormatter::send(stream,
|
||||||
|
F("<@ 0 0 \"DCC-EX v" VERSION "\">\n"
|
||||||
|
"<@ 0 1 \"Lic GPLv3\">\n"));
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
default: //anything else will diagnose and drop out to <X>
|
default: //anything else will diagnose and drop out to <X>
|
||||||
|
if (opcode >= ' ' && opcode <= '~') {
|
||||||
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||||
for (int i = 0; i < params; i++)
|
for (int i = 0; i < params; i++)
|
||||||
DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]);
|
DIAG(F("p[%d]=%d (0x%x)"), i, p[i], p[i]);
|
||||||
|
} else {
|
||||||
|
DIAG(F("Unprintable %x"), opcode);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
} // end of opcode switch
|
} // end of opcode switch
|
||||||
|
|
||||||
// Any fallout here sends an <X>
|
out:// Any fallout here sends an <X>
|
||||||
StringFormatter::send(stream, F("<X>\n"));
|
StringFormatter::send(stream, F("<X>\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,14 +931,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||||
switch (p[1]) {
|
switch (p[1]) {
|
||||||
// Turnout messages use 1=throw, 0=close.
|
// Turnout messages use 1=throw, 0=close.
|
||||||
case 0:
|
case 0:
|
||||||
case HASH_KEYWORD_C:
|
case "C"_hk:
|
||||||
state = true;
|
state = true;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
case HASH_KEYWORD_T:
|
case "T"_hk:
|
||||||
state= false;
|
state= false;
|
||||||
break;
|
break;
|
||||||
case HASH_KEYWORD_X:
|
case "X"_hk:
|
||||||
{
|
{
|
||||||
Turnout *tt = Turnout::get(p[0]);
|
Turnout *tt = Turnout::get(p[0]);
|
||||||
if (tt) {
|
if (tt) {
|
||||||
|
@ -910,14 +955,14 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
|
||||||
}
|
}
|
||||||
|
|
||||||
default: // Anything else is some kind of turnout create function.
|
default: // Anything else is some kind of turnout create function.
|
||||||
if (params == 6 && p[1] == HASH_KEYWORD_SERVO) { // <T id SERVO n n n n>
|
if (params == 6 && p[1] == "SERVO"_hk) { // <T id SERVO n n n n>
|
||||||
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
|
if (!ServoTurnout::create(p[0], (VPIN)p[2], (uint16_t)p[3], (uint16_t)p[4], (uint8_t)p[5]))
|
||||||
return false;
|
return false;
|
||||||
} else
|
} else
|
||||||
if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
|
if (params == 3 && p[1] == "VPIN"_hk) { // <T id VPIN n>
|
||||||
if (!VpinTurnout::create(p[0], p[2])) return false;
|
if (!VpinTurnout::create(p[0], p[2])) return false;
|
||||||
} else
|
} else
|
||||||
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
|
if (params >= 3 && p[1] == "DCC"_hk) {
|
||||||
// <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T>
|
// <T id DCC addr subadd> 0<=addr<=511, 0<=subadd<=3 (like <a> command).<T>
|
||||||
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
|
if (params==4 && p[2]>=0 && p[2]<512 && p[3]>=0 && p[3]<4) { // <T id DCC n m>
|
||||||
if (!DCCTurnout::create(p[0], p[2], p[3])) return false;
|
if (!DCCTurnout::create(p[0], p[2], p[3])) return false;
|
||||||
|
@ -976,120 +1021,250 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||||
{
|
(void)stream; // arg not used, maybe later?
|
||||||
if (params == 0)
|
if (params == 0)
|
||||||
return false;
|
return false;
|
||||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
|
||||||
switch (p[0])
|
switch (p[0])
|
||||||
{
|
{
|
||||||
case HASH_KEYWORD_CABS: // <D CABS>
|
|
||||||
DCC::displayCabList(stream);
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_RAM: // <D RAM>
|
|
||||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
|
||||||
return true;
|
|
||||||
|
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
|
case "PROGBOOST"_hk:
|
||||||
|
TrackManager::progTrackBoosted=true;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
case "RESET"_hk:
|
||||||
|
DCCTimer::reset();
|
||||||
|
break; // and <X> if we didnt restart
|
||||||
|
case "SPEED28"_hk:
|
||||||
|
DCC::setGlobalSpeedsteps(28);
|
||||||
|
DIAG(F("28 Speedsteps"));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "SPEED128"_hk:
|
||||||
|
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) {
|
if (params >= 3) {
|
||||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
if (p[1] == "LIMIT"_hk) {
|
||||||
DCCACK::setAckLimit(p[2]);
|
DCCACK::setAckLimit(p[2]);
|
||||||
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
LCD(1, F("Ack Limit=%dmA"), p[2]); // <D ACK LIMIT 42>
|
||||||
} else if (p[1] == HASH_KEYWORD_MIN) {
|
} else if (p[1] == "MIN"_hk) {
|
||||||
DCCACK::setMinAckPulseDuration(p[2]);
|
DCCACK::setMinAckPulseDuration(p[2]);
|
||||||
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
LCD(0, F("Ack Min=%uus"), p[2]); // <D ACK MIN 1500>
|
||||||
} else if (p[1] == HASH_KEYWORD_MAX) {
|
} else if (p[1] == "MAX"_hk) {
|
||||||
DCCACK::setMaxAckPulseDuration(p[2]);
|
DCCACK::setMaxAckPulseDuration(p[2]);
|
||||||
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
LCD(0, F("Ack Max=%uus"), p[2]); // <D ACK MAX 9000>
|
||||||
} else if (p[1] == HASH_KEYWORD_RETRY) {
|
} else if (p[1] == "RETRY"_hk) {
|
||||||
if (p[2] >255) p[2]=3;
|
if (p[2] >255) p[2]=3;
|
||||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
|
||||||
|
|
||||||
|
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
|
||||||
Diag::ACK = onOff;
|
Diag::ACK = onOff;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
|
||||||
Diag::CMD = onOff;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
#ifdef HAS_ENOUGH_MEMORY
|
|
||||||
case HASH_KEYWORD_WIFI: // <D WIFI ON/OFF>
|
|
||||||
Diag::WIFI = onOff;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_ETHERNET: // <D ETHERNET ON/OFF>
|
|
||||||
Diag::ETHERNET = onOff;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_WIT: // <D WIT ON/OFF>
|
|
||||||
Diag::WITHROTTLE = onOff;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_LCN: // <D LCN ON/OFF>
|
|
||||||
Diag::LCN = onOff;
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
#ifndef DISABLE_PROG
|
|
||||||
case HASH_KEYWORD_PROGBOOST:
|
|
||||||
TrackManager::progTrackBoosted=true;
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
case HASH_KEYWORD_RESET:
|
|
||||||
DCCTimer::reset();
|
|
||||||
break; // and <X> if we didnt restart
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef DISABLE_EEPROM
|
|
||||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
|
||||||
if (params >= 2)
|
|
||||||
EEStore::dump(p[1]);
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case HASH_KEYWORD_SPEED28:
|
|
||||||
DCC::setGlobalSpeedsteps(28);
|
|
||||||
StringFormatter::send(stream, F("28 Speedsteps"));
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_SPEED128:
|
|
||||||
DCC::setGlobalSpeedsteps(128);
|
|
||||||
StringFormatter::send(stream, F("128 Speedsteps"));
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
|
|
||||||
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
|
|
||||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
|
|
||||||
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
|
||||||
break;
|
|
||||||
|
|
||||||
#if !defined(IO_NO_HAL)
|
|
||||||
case HASH_KEYWORD_HAL:
|
|
||||||
if (p[1] == HASH_KEYWORD_SHOW)
|
|
||||||
IODevice::DumpAll();
|
|
||||||
else if (p[1] == HASH_KEYWORD_RESET)
|
|
||||||
IODevice::reset();
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case HASH_KEYWORD_TT: // <D TT vpin steps activity>
|
|
||||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: // invalid/unknown
|
default: // invalid/unknown
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||||
|
{
|
||||||
|
if (params == 0)
|
||||||
|
return false;
|
||||||
|
bool onOff = (params > 0) && (p[1] == 1 || p[1] == "ON"_hk); // dont care if other stuff or missing... just means off
|
||||||
|
switch (p[0])
|
||||||
|
{
|
||||||
|
case "CABS"_hk: // <D CABS>
|
||||||
|
DCC::displayCabList(stream);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "RAM"_hk: // <D RAM>
|
||||||
|
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "CMD"_hk: // <D CMD ON/OFF>
|
||||||
|
Diag::CMD = onOff;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
|
case "WIFI"_hk: // <D WIFI ON/OFF>
|
||||||
|
Diag::WIFI = onOff;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "ETHERNET"_hk: // <D ETHERNET ON/OFF>
|
||||||
|
Diag::ETHERNET = onOff;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "WIT"_hk: // <D WIT ON/OFF>
|
||||||
|
Diag::WITHROTTLE = onOff;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "LCN"_hk: // <D LCN ON/OFF>
|
||||||
|
Diag::LCN = onOff;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
#ifndef DISABLE_EEPROM
|
||||||
|
case "EEPROM"_hk: // <D EEPROM NumEntries>
|
||||||
|
if (params >= 2)
|
||||||
|
EEStore::dump(p[1]);
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
case "SERVO"_hk: // <D SERVO vpin position [profile]>
|
||||||
|
|
||||||
|
case "ANOUT"_hk: // <D ANOUT vpin position [profile]>
|
||||||
|
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "ANIN"_hk: // <D ANIN vpin> Display analogue input value
|
||||||
|
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
#if !defined(IO_NO_HAL)
|
||||||
|
case "HAL"_hk:
|
||||||
|
if (p[1] == "SHOW"_hk)
|
||||||
|
IODevice::DumpAll();
|
||||||
|
else if (p[1] == "RESET"_hk)
|
||||||
|
IODevice::reset();
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case "TT"_hk: // <D TT vpin steps activity>
|
||||||
|
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default: // invalid/unknown
|
||||||
|
return parseC(stream, params, p);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// Turntable - no support if no HAL
|
||||||
|
// <I> - list all
|
||||||
|
// <I id> - broadcast type and current position
|
||||||
|
// <I id DCC> - create DCC - This is TBA
|
||||||
|
// <I id steps> - operate (DCC)
|
||||||
|
// <I id steps activity> - operate (EXTT)
|
||||||
|
// <I id ADD position value> - add position
|
||||||
|
// <I id EXTT i2caddress vpin home> - create EXTT
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||||
|
{
|
||||||
|
switch (params)
|
||||||
|
{
|
||||||
|
case 0: // <I> list turntable objects
|
||||||
|
return Turntable::printAll(stream);
|
||||||
|
|
||||||
|
case 1: // <I id> broadcast type and current position
|
||||||
|
{
|
||||||
|
Turntable *tto = Turntable::get(p[0]);
|
||||||
|
if (tto) {
|
||||||
|
bool type = tto->isEXTT();
|
||||||
|
uint8_t position = tto->getPosition();
|
||||||
|
StringFormatter::send(stream, F("<I %d %d>\n"), type, position);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 2: // <I id position> - rotate a DCC turntable
|
||||||
|
{
|
||||||
|
Turntable *tto = Turntable::get(p[0]);
|
||||||
|
if (tto && !tto->isEXTT()) {
|
||||||
|
if (!tto->setPosition(p[0], p[1])) return false;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 3: // <I id position activity> | <I id DCC home> - rotate to position for EX-Turntable or create DCC turntable
|
||||||
|
{
|
||||||
|
Turntable *tto = Turntable::get(p[0]);
|
||||||
|
if (p[1] == "DCC"_hk) {
|
||||||
|
if (tto || p[2] < 0 || p[2] > 3600) return false;
|
||||||
|
if (!DCCTurntable::create(p[0])) return false;
|
||||||
|
Turntable *tto = Turntable::get(p[0]);
|
||||||
|
tto->addPosition(0, 0, p[2]);
|
||||||
|
StringFormatter::send(stream, F("<I>\n"));
|
||||||
|
} else {
|
||||||
|
if (!tto) return false;
|
||||||
|
if (!tto->isEXTT()) return false;
|
||||||
|
if (!tto->setPosition(p[0], p[1], p[2])) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 4: // <I id EXTT vpin home> create an EXTT turntable
|
||||||
|
{
|
||||||
|
Turntable *tto = Turntable::get(p[0]);
|
||||||
|
if (p[1] == "EXTT"_hk) {
|
||||||
|
if (tto || p[3] < 0 || p[3] > 3600) return false;
|
||||||
|
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
|
||||||
|
Turntable *tto = Turntable::get(p[0]);
|
||||||
|
tto->addPosition(0, 0, p[3]);
|
||||||
|
StringFormatter::send(stream, F("<I>\n"));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 5: // <I id ADD position value angle> add a position
|
||||||
|
{
|
||||||
|
Turntable *tto = Turntable::get(p[0]);
|
||||||
|
if (p[1] == "ADD"_hk) {
|
||||||
|
// tto must exist, no more than 48 positions, angle 0 - 3600
|
||||||
|
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
|
||||||
|
tto->addPosition(p[2], p[3], p[4]);
|
||||||
|
StringFormatter::send(stream, F("<I>\n"));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default: // Anything else is invalid
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// CALLBACKS must be static
|
// CALLBACKS must be static
|
||||||
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
||||||
{
|
{
|
||||||
|
@ -1176,3 +1351,11 @@ void DCCEXParser::callback_Wloco(int16_t result)
|
||||||
StringFormatter::send(getAsyncReplyStream(), F("<w %d>\n"), result);
|
StringFormatter::send(getAsyncReplyStream(), F("<w %d>\n"), result);
|
||||||
commitAsyncReplyStream();
|
commitAsyncReplyStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCEXParser::callback_Wconsist(int16_t result)
|
||||||
|
{
|
||||||
|
if (result==1) result=stashP[1]; // pick up original requested id from command
|
||||||
|
StringFormatter::send(getAsyncReplyStream(), F("<w CONSIST %d%S>\n"),
|
||||||
|
result, stashP[2]=="REVERSE"_hk ? F(" REVERSE") : F(""));
|
||||||
|
commitAsyncReplyStream();
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "RingStream.h"
|
#include "RingStream.h"
|
||||||
|
#include "defines.h"
|
||||||
|
|
||||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
|
typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
|
||||||
|
@ -48,7 +49,11 @@ struct DCCEXParser
|
||||||
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||||
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||||
|
static bool parseC(Print * stream, int16_t params, int16_t p[]);
|
||||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||||
|
#endif
|
||||||
|
|
||||||
static Print * getAsyncReplyStream();
|
static Print * getAsyncReplyStream();
|
||||||
static void commitAsyncReplyStream();
|
static void commitAsyncReplyStream();
|
||||||
|
@ -66,6 +71,7 @@ struct DCCEXParser
|
||||||
static void callback_R(int16_t result);
|
static void callback_R(int16_t result);
|
||||||
static void callback_Rloco(int16_t result);
|
static void callback_Rloco(int16_t result);
|
||||||
static void callback_Wloco(int16_t result);
|
static void callback_Wloco(int16_t result);
|
||||||
|
static void callback_Wconsist(int16_t result);
|
||||||
static void callback_Vbit(int16_t result);
|
static void callback_Vbit(int16_t result);
|
||||||
static void callback_Vbyte(int16_t result);
|
static void callback_Vbyte(int16_t result);
|
||||||
static FILTER_CALLBACK filterCallback;
|
static FILTER_CALLBACK filterCallback;
|
||||||
|
|
48
DCCRMT.cpp
48
DCCRMT.cpp
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* © 2021-2022, Harald Barth.
|
* © 2021-2024, Harald Barth.
|
||||||
*
|
*
|
||||||
* This file is part of DCC-EX
|
* This file is part of DCC-EX
|
||||||
*
|
*
|
||||||
|
@ -25,6 +25,18 @@
|
||||||
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
|
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
|
||||||
#include "soc/gpio_sig_map.h"
|
#include "soc/gpio_sig_map.h"
|
||||||
|
|
||||||
|
// check for right type of ESP32
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#ifndef SOC_RMT_MEM_WORDS_PER_CHANNEL
|
||||||
|
#error This symobol should be defined
|
||||||
|
#endif
|
||||||
|
#if SOC_RMT_MEM_WORDS_PER_CHANNEL < 64
|
||||||
|
#warning This is not an ESP32-WROOM but some other unsupported variant
|
||||||
|
#warning You are outside of the DCC-EX supported hardware
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const byte RMT_CHAN_PER_DCC_CHAN = 2;
|
||||||
|
|
||||||
// Number of bits resulting out of X bytes of DCC payload data
|
// Number of bits resulting out of X bytes of DCC payload data
|
||||||
// Each byte has one bit extra and at the end we have one EOF marker
|
// Each byte has one bit extra and at the end we have one EOF marker
|
||||||
#define DATA_LEN(X) ((X)*9+1)
|
#define DATA_LEN(X) ((X)*9+1)
|
||||||
|
@ -75,12 +87,30 @@ void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
|
||||||
RMTChannel::RMTChannel(pinpair pins, bool isMain) {
|
RMTChannel::RMTChannel(pinpair pins, bool isMain) {
|
||||||
byte ch;
|
byte ch;
|
||||||
byte plen;
|
byte plen;
|
||||||
|
|
||||||
|
// Below we check if the DCC packet actually fits into the RMT hardware
|
||||||
|
// Currently MAX_PACKET_SIZE = 5 so with checksum there are
|
||||||
|
// MAX_PACKET_SIZE+1 data packets. Each need DATA_LEN (9) bits.
|
||||||
|
// To that we add the preamble length, the fencepost DCC end bit
|
||||||
|
// and the RMT EOF marker.
|
||||||
|
// SOC_RMT_MEM_WORDS_PER_CHANNEL is either 64 (original WROOM) or
|
||||||
|
// 48 (all other ESP32 like the -C3 or -S2
|
||||||
|
// The formula to get the possible MAX_PACKET_SIZE is
|
||||||
|
//
|
||||||
|
// ALLOCATED = RMT_CHAN_PER_DCC_CHAN * SOC_RMT_MEM_WORDS_PER_CHANNEL
|
||||||
|
// MAX_PACKET_SIZE = floor((ALLOCATED - PREAMBLE_LEN - 2)/9 - 1)
|
||||||
|
//
|
||||||
|
|
||||||
if (isMain) {
|
if (isMain) {
|
||||||
ch = 0;
|
ch = 0;
|
||||||
plen = PREAMBLE_BITS_MAIN;
|
plen = PREAMBLE_BITS_MAIN;
|
||||||
|
static_assert (DATA_LEN(MAX_PACKET_SIZE+1) + PREAMBLE_BITS_MAIN + 2 <= RMT_CHAN_PER_DCC_CHAN * SOC_RMT_MEM_WORDS_PER_CHANNEL,
|
||||||
|
"Number of DCC packet bits greater than ESP32 RMT memory available");
|
||||||
} else {
|
} else {
|
||||||
ch = 2;
|
ch = RMT_CHAN_PER_DCC_CHAN; // number == offset
|
||||||
plen = PREAMBLE_BITS_PROG;
|
plen = PREAMBLE_BITS_PROG;
|
||||||
|
static_assert (DATA_LEN(MAX_PACKET_SIZE+1) + PREAMBLE_BITS_PROG + 2 <= RMT_CHAN_PER_DCC_CHAN * SOC_RMT_MEM_WORDS_PER_CHANNEL,
|
||||||
|
"Number of DCC packet bits greater than ESP32 RMT memory available");
|
||||||
}
|
}
|
||||||
|
|
||||||
// preamble
|
// preamble
|
||||||
|
@ -123,20 +153,10 @@ RMTChannel::RMTChannel(pinpair pins, bool isMain) {
|
||||||
config.channel = channel = (rmt_channel_t)ch;
|
config.channel = channel = (rmt_channel_t)ch;
|
||||||
config.clk_div = RMT_CLOCK_DIVIDER;
|
config.clk_div = RMT_CLOCK_DIVIDER;
|
||||||
config.gpio_num = (gpio_num_t)pins.pin;
|
config.gpio_num = (gpio_num_t)pins.pin;
|
||||||
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
|
config.mem_block_num = RMT_CHAN_PER_DCC_CHAN;
|
||||||
// number of bits needed is 22preamble + start +
|
// use config
|
||||||
// 11*9 + extrazero + EOT = 124
|
|
||||||
// 2 mem block of 64 RMT items should be enough
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK(rmt_config(&config));
|
ESP_ERROR_CHECK(rmt_config(&config));
|
||||||
addPin(pins.invpin, true);
|
addPin(pins.invpin, true);
|
||||||
/*
|
|
||||||
// test: config another gpio pin
|
|
||||||
gpio_num_t gpioNum = (gpio_num_t)(pin-1);
|
|
||||||
PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[gpioNum], PIN_FUNC_GPIO);
|
|
||||||
gpio_set_direction(gpioNum, GPIO_MODE_OUTPUT);
|
|
||||||
gpio_matrix_out(gpioNum, RMT_SIG_OUT0_IDX, 0, 0);
|
|
||||||
*/
|
|
||||||
|
|
||||||
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
|
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
|
||||||
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
|
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
|
||||||
|
|
18
DCCTimer.h
18
DCCTimer.h
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* © 2022-2023 Paul M. Antoine
|
* © 2022-2024 Paul M. Antoine
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021-2023 Harald Barth
|
* © 2021-2023 Harald Barth
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
|
@ -62,8 +62,14 @@ class DCCTimer {
|
||||||
static bool isPWMPin(byte pin);
|
static bool isPWMPin(byte pin);
|
||||||
static void setPWM(byte pin, bool high);
|
static void setPWM(byte pin, bool high);
|
||||||
static void clearPWM();
|
static void clearPWM();
|
||||||
|
static void startRailcomTimer(byte brakePin);
|
||||||
|
static void ackRailcomTimer();
|
||||||
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||||
static void DCCEXanalogWrite(uint8_t pin, int value);
|
static void DCCEXanalogWrite(uint8_t pin, int value, bool invert);
|
||||||
|
static void DCCEXledcDetachPin(uint8_t pin);
|
||||||
|
static void DCCEXanalogCopyChannel(int8_t frompin, int8_t topin);
|
||||||
|
static void DCCEXInrushControlOn(uint8_t pin, int duty, bool invert);
|
||||||
|
static void DCCEXledcAttachPin(uint8_t pin, int8_t channel, bool inverted);
|
||||||
|
|
||||||
// Update low ram level. Allow for extra bytes to be specified
|
// Update low ram level. Allow for extra bytes to be specified
|
||||||
// by estimation or inspection, that may be used by other
|
// by estimation or inspection, that may be used by other
|
||||||
|
@ -85,6 +91,7 @@ class DCCTimer {
|
||||||
static void reset();
|
static void reset();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static void DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency);
|
||||||
static int freeMemory();
|
static int freeMemory();
|
||||||
static volatile int minimum_free_memory;
|
static volatile int minimum_free_memory;
|
||||||
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||||
|
@ -125,8 +132,15 @@ private:
|
||||||
// On platforms that scan, it is called from waveform ISR
|
// On platforms that scan, it is called from waveform ISR
|
||||||
// only on a regular basis.
|
// only on a regular basis.
|
||||||
static void scan();
|
static void scan();
|
||||||
|
#if defined (ARDUINO_ARCH_STM32)
|
||||||
|
// bit array of used pins (max 32)
|
||||||
|
static uint32_t usedpins;
|
||||||
|
static uint32_t * analogchans; // Array of channel numbers to be scanned
|
||||||
|
static ADC_TypeDef * * adcchans; // Array to capture which ADC is each input channel on
|
||||||
|
#else
|
||||||
// bit array of used pins (max 16)
|
// bit array of used pins (max 16)
|
||||||
static uint16_t usedpins;
|
static uint16_t usedpins;
|
||||||
|
#endif
|
||||||
static uint8_t highestPin;
|
static uint8_t highestPin;
|
||||||
// cached analog values (malloc:ed to actual number of ADC channels)
|
// cached analog values (malloc:ed to actual number of ADC channels)
|
||||||
static int *analogvals;
|
static int *analogvals;
|
||||||
|
|
146
DCCTimerAVR.cpp
146
DCCTimerAVR.cpp
|
@ -29,6 +29,7 @@
|
||||||
#include <avr/boot.h>
|
#include <avr/boot.h>
|
||||||
#include <avr/wdt.h>
|
#include <avr/wdt.h>
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
|
#include "DIAG.h"
|
||||||
#ifdef DEBUG_ADC
|
#ifdef DEBUG_ADC
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#endif
|
#endif
|
||||||
|
@ -39,6 +40,9 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
#define TIMER1_A_PIN 11
|
#define TIMER1_A_PIN 11
|
||||||
#define TIMER1_B_PIN 12
|
#define TIMER1_B_PIN 12
|
||||||
#define TIMER1_C_PIN 13
|
#define TIMER1_C_PIN 13
|
||||||
|
#define TIMER2_A_PIN 10
|
||||||
|
#define TIMER2_B_PIN 9
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#define TIMER1_A_PIN 9
|
#define TIMER1_A_PIN 9
|
||||||
#define TIMER1_B_PIN 10
|
#define TIMER1_B_PIN 10
|
||||||
|
@ -55,6 +59,67 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interrupts();
|
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 called by timer interrupt every 58uS
|
||||||
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
ISR(TIMER1_OVF_vect){ interruptHandler(); }
|
||||||
|
|
||||||
|
@ -120,11 +185,88 @@ int DCCTimer::freeMemory() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCTimer::reset() {
|
void DCCTimer::reset() {
|
||||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
// 250ms chosen to circumwent bootloader bug which
|
||||||
delay(50); // wait for the prescaller time to expire
|
// hangs at too short timepout (like 15ms)
|
||||||
|
wdt_enable( WDTO_250MS); // set Arduino watchdog timer for 250ms
|
||||||
|
delay(500); // wait for it to happen
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||||
|
}
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||||
|
#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)
|
||||||
|
// Speed mapping is done like this:
|
||||||
|
// No functions buttons: 000 0 -> low 131Hz
|
||||||
|
// Only F29 pressed 001 1 -> mid 490Hz
|
||||||
|
// F30 with or w/o F29 01x 2-3 -> high 3400Hz
|
||||||
|
// F31 with or w/o F29/30 1xx 4-7 -> supersonic 62500Hz
|
||||||
|
uint8_t abits;
|
||||||
|
uint8_t bbits;
|
||||||
|
if (pin == 9 || pin == 10) { // timer 2 is different
|
||||||
|
|
||||||
|
if (fbits >= 4)
|
||||||
|
abits = B00000011;
|
||||||
|
else
|
||||||
|
abits = B00000001;
|
||||||
|
|
||||||
|
if (fbits >= 4)
|
||||||
|
bbits = B0001;
|
||||||
|
else if (fbits >= 2)
|
||||||
|
bbits = B0010;
|
||||||
|
else if (fbits == 1)
|
||||||
|
bbits = B0100;
|
||||||
|
else // fbits == 0
|
||||||
|
bbits = B0110;
|
||||||
|
|
||||||
|
TCCR2A = (TCCR2A & B11111100) | abits; // set WGM0 and WGM1
|
||||||
|
TCCR2B = (TCCR2B & B11110000) | bbits; // set WGM2 and 3 bits of prescaler
|
||||||
|
DIAG(F("Timer 2 A=%x B=%x"), TCCR2A, TCCR2B);
|
||||||
|
|
||||||
|
} else { // not timer 9 or 10
|
||||||
|
abits = B01;
|
||||||
|
|
||||||
|
if (fbits >= 4)
|
||||||
|
bbits = B1001;
|
||||||
|
else if (fbits >= 2)
|
||||||
|
bbits = B0010;
|
||||||
|
else if (fbits == 1)
|
||||||
|
bbits = B0011;
|
||||||
|
else
|
||||||
|
bbits = B0100;
|
||||||
|
|
||||||
|
switch (pin) {
|
||||||
|
// case 9 and 10 taken care of above by if()
|
||||||
|
case 6:
|
||||||
|
case 7:
|
||||||
|
case 8:
|
||||||
|
// Timer4
|
||||||
|
TCCR4A = (TCCR4A & B11111100) | abits; // set WGM0 and WGM1
|
||||||
|
TCCR4B = (TCCR4B & B11100000) | bbits; // set WGM2 and WGM3 and divisor
|
||||||
|
//DIAG(F("Timer 4 A=%x B=%x"), TCCR4A, TCCR4B);
|
||||||
|
break;
|
||||||
|
case 46:
|
||||||
|
case 45:
|
||||||
|
case 44:
|
||||||
|
// Timer5
|
||||||
|
TCCR5A = (TCCR5A & B11111100) | abits; // set WGM0 and WGM1
|
||||||
|
TCCR5B = (TCCR5B & B11100000) | bbits; // set WGM2 and WGM3 and divisor
|
||||||
|
//DIAG(F("Timer 5 A=%x B=%x"), TCCR5A, TCCR5B);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||||
#define NUM_ADC_INPUTS 16
|
#define NUM_ADC_INPUTS 16
|
||||||
#else
|
#else
|
||||||
|
|
113
DCCTimerESP.cpp
113
DCCTimerESP.cpp
|
@ -156,10 +156,28 @@ void DCCTimer::reset() {
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||||
|
if (f >= 16)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||||
|
/*
|
||||||
|
else if (f == 7) // not used on ESP32
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 62500);
|
||||||
|
*/
|
||||||
|
else if (f >= 4)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 32000);
|
||||||
|
else if (f >= 3)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||||
|
else if (f >= 2)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
||||||
|
else if (f == 1)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||||
|
else
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 131);
|
||||||
|
}
|
||||||
|
|
||||||
#include "esp32-hal.h"
|
#include "esp32-hal.h"
|
||||||
#include "soc/soc_caps.h"
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
|
|
||||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||||
#else
|
#else
|
||||||
|
@ -169,7 +187,7 @@ void DCCTimer::reset() {
|
||||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||||
static int cnt_channel = LEDC_CHANNELS;
|
static int cnt_channel = LEDC_CHANNELS;
|
||||||
|
|
||||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency) {
|
||||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
if (pin_to_channel[pin] != 0) {
|
if (pin_to_channel[pin] != 0) {
|
||||||
ledcSetup(pin_to_channel[pin], frequency, 8);
|
ledcSetup(pin_to_channel[pin], frequency, 8);
|
||||||
|
@ -177,23 +195,104 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
void DCCTimer::DCCEXledcDetachPin(uint8_t pin) {
|
||||||
|
DIAG(F("Clear pin %d channel"), pin);
|
||||||
|
pin_to_channel[pin] = 0;
|
||||||
|
pinMatrixOutDetach(pin, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static byte LEDCToMux[] = {
|
||||||
|
LEDC_HS_SIG_OUT0_IDX,
|
||||||
|
LEDC_HS_SIG_OUT1_IDX,
|
||||||
|
LEDC_HS_SIG_OUT2_IDX,
|
||||||
|
LEDC_HS_SIG_OUT3_IDX,
|
||||||
|
LEDC_HS_SIG_OUT4_IDX,
|
||||||
|
LEDC_HS_SIG_OUT5_IDX,
|
||||||
|
LEDC_HS_SIG_OUT6_IDX,
|
||||||
|
LEDC_HS_SIG_OUT7_IDX,
|
||||||
|
LEDC_LS_SIG_OUT0_IDX,
|
||||||
|
LEDC_LS_SIG_OUT1_IDX,
|
||||||
|
LEDC_LS_SIG_OUT2_IDX,
|
||||||
|
LEDC_LS_SIG_OUT3_IDX,
|
||||||
|
LEDC_LS_SIG_OUT4_IDX,
|
||||||
|
LEDC_LS_SIG_OUT5_IDX,
|
||||||
|
LEDC_LS_SIG_OUT6_IDX,
|
||||||
|
LEDC_LS_SIG_OUT7_IDX,
|
||||||
|
};
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXledcAttachPin(uint8_t pin, int8_t channel, bool inverted) {
|
||||||
|
DIAG(F("Attaching pin %d to channel %d %c"), pin, channel, inverted ? 'I' : ' ');
|
||||||
|
ledcAttachPin(pin, channel);
|
||||||
|
if (inverted) // we attach again but with inversion
|
||||||
|
gpio_matrix_out(pin, LEDCToMux[channel], inverted, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogCopyChannel(int8_t frompin, int8_t topin) {
|
||||||
|
// arguments are signed depending on inversion of pins
|
||||||
|
DIAG(F("Pin %d copied to %d"), frompin, topin);
|
||||||
|
bool inverted = false;
|
||||||
|
if (frompin<0)
|
||||||
|
frompin = -frompin;
|
||||||
|
if (topin<0) {
|
||||||
|
inverted = true;
|
||||||
|
topin = -topin;
|
||||||
|
}
|
||||||
|
int channel = pin_to_channel[frompin]; // after abs(frompin)
|
||||||
|
pin_to_channel[topin] = channel;
|
||||||
|
DCCTimer::DCCEXledcAttachPin(topin, channel, inverted);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
|
||||||
|
// This allocates channels 15, 13, 11, ....
|
||||||
|
// so each channel gets its own timer.
|
||||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
if (pin_to_channel[pin] == 0) {
|
if (pin_to_channel[pin] == 0) {
|
||||||
|
int search_channel;
|
||||||
|
int n;
|
||||||
if (!cnt_channel) {
|
if (!cnt_channel) {
|
||||||
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
pin_to_channel[pin] = --cnt_channel;
|
// search for free channels top down
|
||||||
ledcSetup(cnt_channel, 1000, 8);
|
for (search_channel=LEDC_CHANNELS-1; search_channel >=cnt_channel; search_channel -= 2) {
|
||||||
ledcAttachPin(pin, cnt_channel);
|
bool chanused = false;
|
||||||
|
for (n=0; n < SOC_GPIO_PIN_COUNT; n++) {
|
||||||
|
if (pin_to_channel[n] == search_channel) { // current search_channel used
|
||||||
|
chanused = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (chanused)
|
||||||
|
continue;
|
||||||
|
if (n == SOC_GPIO_PIN_COUNT) // current search_channel unused
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (search_channel >= cnt_channel) {
|
||||||
|
pin_to_channel[pin] = search_channel;
|
||||||
|
DIAG(F("Pin %d assigned to search channel %d"), pin, search_channel);
|
||||||
} else {
|
} else {
|
||||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
pin_to_channel[pin] = --cnt_channel; // This sets 15, 13, ...
|
||||||
|
DIAG(F("Pin %d assigned to new channel %d"), pin, cnt_channel);
|
||||||
|
--cnt_channel; // Now we are at 14, 12, ...
|
||||||
|
}
|
||||||
|
ledcSetup(pin_to_channel[pin], 1000, 8);
|
||||||
|
DCCEXledcAttachPin(pin, pin_to_channel[pin], invert);
|
||||||
|
} else {
|
||||||
|
// This else is only here so we can enable diag
|
||||||
|
// Pin should be already attached to channel
|
||||||
|
// DIAG(F("Pin %d assigned to old channel %d"), pin, pin_to_channel[pin]);
|
||||||
}
|
}
|
||||||
ledcWrite(pin_to_channel[pin], value);
|
ledcWrite(pin_to_channel[pin], value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXInrushControlOn(uint8_t pin, int duty, bool inverted) {
|
||||||
|
// this uses hardcoded channel 0
|
||||||
|
ledcSetup(0, 62500, 8);
|
||||||
|
DCCEXledcAttachPin(pin, 0, inverted);
|
||||||
|
ledcWrite(0, duty);
|
||||||
|
}
|
||||||
|
|
||||||
int ADCee::init(uint8_t pin) {
|
int ADCee::init(uint8_t pin) {
|
||||||
pinMode(pin, ANALOG);
|
pinMode(pin, ANALOG);
|
||||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||||
|
|
|
@ -80,6 +80,15 @@ extern char *__malloc_heap_start;
|
||||||
interruptHandler();
|
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) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
(void) pin;
|
(void) pin;
|
||||||
return false; // TODO what are the relevant pins?
|
return false; // TODO what are the relevant pins?
|
||||||
|
@ -125,6 +134,11 @@ void DCCTimer::reset() {
|
||||||
while(true){}
|
while(true){}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||||
|
}
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||||
|
}
|
||||||
|
|
||||||
int16_t ADCee::ADCmax() {
|
int16_t ADCee::ADCmax() {
|
||||||
return 4095;
|
return 4095;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interrupts();
|
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)
|
// Timer IRQ handlers replace the dummy handlers (in cortex_handlers)
|
||||||
// copied from rf24 branch
|
// copied from rf24 branch
|
||||||
void TCC0_Handler() {
|
void TCC0_Handler() {
|
||||||
|
@ -156,6 +165,11 @@ void DCCTimer::reset() {
|
||||||
while(true) {};
|
while(true) {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||||
|
}
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||||
|
}
|
||||||
|
|
||||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||||
|
|
||||||
uint16_t ADCee::usedpins = 0;
|
uint16_t ADCee::usedpins = 0;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* © 2023 Neil McKechnie
|
* © 2023 Neil McKechnie
|
||||||
* © 2022-23 Paul M. Antoine
|
* © 2022-2024 Paul M. Antoine
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021, 2023 Harald Barth
|
* © 2021, 2023 Harald Barth
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
|
@ -34,8 +34,22 @@
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#endif
|
#endif
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include <wiring_private.h>
|
||||||
|
|
||||||
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
|
#if defined(ARDUINO_NUCLEO_F401RE)
|
||||||
|
// Nucleo-64 boards don't have additional serial ports defined by default
|
||||||
|
// Serial1 is available on the F401RE, but not hugely convenient.
|
||||||
|
// Rx pin on PB7 is useful, but all the Tx pins map to Arduino digital pins, specifically:
|
||||||
|
// PA9 == D8
|
||||||
|
// PB6 == D10
|
||||||
|
// of which D8 is needed by the standard and EX8874 motor shields. D10 would be used if a second
|
||||||
|
// EX8874 is stacked. So only disable this if using a second motor shield.
|
||||||
|
HardwareSerial Serial1(PB7, PB6); // Rx=PB7, Tx=PB6 -- CN7 pin 17 and CN10 pin 17
|
||||||
|
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||||
|
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||||
|
// Let's define Serial6 as an additional serial port (the only other option for the F401RE)
|
||||||
|
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F401RE
|
||||||
|
#elif defined(ARDUINO_NUCLEO_F411RE)
|
||||||
// Nucleo-64 boards don't have additional serial ports defined by default
|
// 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
|
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
|
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||||
|
@ -50,11 +64,16 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
|
||||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||||
// On the F446RE, Serial3 and Serial5 are easy to use:
|
// On the F446RE, Serial3 and Serial5 are easy to use:
|
||||||
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
|
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
|
||||||
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
|
HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE
|
||||||
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
|
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
|
||||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG)
|
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F446ZE) || \
|
||||||
|
defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
|
||||||
// Nucleo-144 boards don't have Serial1 defined by default
|
// Nucleo-144 boards don't have Serial1 defined by default
|
||||||
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||||
|
HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5
|
||||||
|
#if !defined(ARDUINO_NUCLEO_F412ZG)
|
||||||
|
HardwareSerial Serial2(PD6, PD5); // Rx=PD6, Tx=PD5 -- UART5
|
||||||
|
#endif
|
||||||
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
|
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
|
||||||
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||||
#else
|
#else
|
||||||
|
@ -154,13 +173,28 @@ HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
INTERRUPT_CALLBACK interruptHandler=0;
|
INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
// Let's use STM32's timer #11 until disabused of this notion
|
|
||||||
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
// On STM32F4xx models that have them, Timers 6 and 7 have no PWM output capability,
|
||||||
// this libary, we should be free and clear.
|
// so are good choices for general timer duties - they are used for tone and servo
|
||||||
HardwareTimer timer(TIM11);
|
// in stm32duino so we shall usurp those as DCC-EX doesn't use tone or servo libs.
|
||||||
|
// NB: the F401, F410 and F411 do **not** have Timer 6 or 7, so we use Timer 11
|
||||||
|
#ifndef DCC_EX_TIMER
|
||||||
|
#if defined(TIM6)
|
||||||
|
#define DCC_EX_TIMER TIM6
|
||||||
|
#elif defined(TIM7)
|
||||||
|
#define DCC_EX_TIMER TIM7
|
||||||
|
#elif defined(TIM11)
|
||||||
|
#define DCC_EX_TIMER TIM11
|
||||||
|
#else
|
||||||
|
#warning This STM32F4XX variant does not have Timers 6,7 or 11!!
|
||||||
|
#endif
|
||||||
|
#endif // ifndef DCC_EX_TIMER
|
||||||
|
|
||||||
|
HardwareTimer dcctimer(DCC_EX_TIMER);
|
||||||
|
void DCCTimer_Handler() __attribute__((interrupt));
|
||||||
|
|
||||||
// Timer IRQ handler
|
// Timer IRQ handler
|
||||||
void Timer11_Handler() {
|
void DCCTimer_Handler() {
|
||||||
interruptHandler();
|
interruptHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,22 +202,33 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interruptHandler=callback;
|
interruptHandler=callback;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
|
|
||||||
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
dcctimer.pause();
|
||||||
timer.pause();
|
dcctimer.setPrescaleFactor(1);
|
||||||
timer.setPrescaleFactor(1);
|
|
||||||
// timer.setOverflow(CLOCK_CYCLES * 2);
|
// timer.setOverflow(CLOCK_CYCLES * 2);
|
||||||
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
dcctimer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||||
timer.attachInterrupt(Timer11_Handler);
|
// dcctimer.attachInterrupt(Timer11_Handler);
|
||||||
timer.refresh();
|
dcctimer.attachInterrupt(DCCTimer_Handler);
|
||||||
timer.resume();
|
dcctimer.setInterruptPriority(0, 0); // Set highest preemptive priority!
|
||||||
|
dcctimer.refresh();
|
||||||
|
dcctimer.resume();
|
||||||
|
|
||||||
interrupts();
|
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) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
//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
|
// there's no support yet for High Accuracy, so for now return false
|
||||||
// return digitalPinHasPWM(pin);
|
// return digitalPinHasPWM(pin);
|
||||||
|
(void) pin;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,9 +243,9 @@ void DCCTimer::clearPWM() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10;
|
volatile uint32_t *serno1 = (volatile uint32_t *)UID_BASE;
|
||||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
|
volatile uint32_t *serno2 = (volatile uint32_t *)UID_BASE+4;
|
||||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
// volatile uint32_t *serno3 = (volatile uint32_t *)UID_BASE+8;
|
||||||
|
|
||||||
volatile uint32_t m1 = *serno1;
|
volatile uint32_t m1 = *serno1;
|
||||||
volatile uint32_t m2 = *serno2;
|
volatile uint32_t m2 = *serno2;
|
||||||
|
@ -235,21 +280,109 @@ void DCCTimer::reset() {
|
||||||
while(true) {};
|
while(true) {};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||||
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
if (f >= 16)
|
||||||
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, f);
|
||||||
#endif
|
else if (f == 7)
|
||||||
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 62500);
|
||||||
#define NUM_ADC_INPUTS 16
|
else if (f >= 4)
|
||||||
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 32000);
|
||||||
|
else if (f >= 3)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 16000);
|
||||||
|
else if (f >= 2)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 3400);
|
||||||
|
else if (f == 1)
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 480);
|
||||||
|
else
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequencyInternal(pin, 131);
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t ADCee::usedpins = 0;
|
// TODO: rationalise the size of these... could really use sparse arrays etc.
|
||||||
uint8_t ADCee::highestPin = 0;
|
static HardwareTimer * pin_timer[100] = {0};
|
||||||
int * ADCee::analogvals = NULL;
|
static uint32_t channel_frequency[100] = {0};
|
||||||
uint32_t * analogchans = NULL;
|
static uint32_t pin_channel[100] = {0};
|
||||||
bool adc1configured = false;
|
|
||||||
|
|
||||||
int16_t ADCee::ADCmax() {
|
// Using the HardwareTimer library API included in stm32duino core to handle PWM duties
|
||||||
|
// TODO: in order to use the HA code above which Neil kindly wrote, we may have to do something more
|
||||||
|
// sophisticated about detecting any clash between the timer we'd like to use for PWM and the ones
|
||||||
|
// currently used for HA so they don't interfere with one another. For now we'll just make PWM
|
||||||
|
// work well... then work backwards to integrate with HA mode if we can.
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency)
|
||||||
|
{
|
||||||
|
if (pin_timer[pin] == NULL) {
|
||||||
|
// Automatically retrieve TIM instance and channel associated to pin
|
||||||
|
// This is used to be compatible with all STM32 series automatically.
|
||||||
|
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
|
||||||
|
if (Instance == NULL) {
|
||||||
|
// We shouldn't get here (famous last words) as it ought to have been caught by brakeCanPWM()!
|
||||||
|
DIAG(F("DCCEXanalogWriteFrequency::Pin %d has no PWM function!"), pin);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pin_channel[pin] = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
|
||||||
|
|
||||||
|
// Instantiate HardwareTimer object. Thanks to 'new' instantiation,
|
||||||
|
// HardwareTimer is not destructed when setup function is finished.
|
||||||
|
pin_timer[pin] = new HardwareTimer(Instance);
|
||||||
|
// Configure and start PWM
|
||||||
|
// MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we can simplify the function call
|
||||||
|
if (pin_timer[pin] != NULL)
|
||||||
|
{
|
||||||
|
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
|
||||||
|
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d, frequency %d"), pin, pin_channel[pin], frequency);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Frequency change request
|
||||||
|
if (frequency != channel_frequency[pin])
|
||||||
|
{
|
||||||
|
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||||
|
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
|
||||||
|
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel_frequency[pin] = frequency;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value, bool invert) {
|
||||||
|
if (invert)
|
||||||
|
value = 255-value;
|
||||||
|
// Calculate percentage duty cycle from value given
|
||||||
|
uint32_t duty_cycle = (value * 100 / 256) + 1;
|
||||||
|
if (pin_timer[pin] != NULL) {
|
||||||
|
// if (duty_cycle == 100)
|
||||||
|
// {
|
||||||
|
// pin_timer[pin]->pauseChannel(pin_channel[pin]);
|
||||||
|
// DIAG(F("DCCEXanalogWrite::Pausing timer channel on pin %d"), pin);
|
||||||
|
// }
|
||||||
|
// else
|
||||||
|
// {
|
||||||
|
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||||
|
// pin_timer[pin]->resumeChannel(pin_channel[pin]);
|
||||||
|
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
|
||||||
|
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Now we can handle more ADCs, maybe this works!
|
||||||
|
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||||
|
|
||||||
|
uint32_t ADCee::usedpins = 0; // Max of 32 ADC input channels!
|
||||||
|
uint8_t ADCee::highestPin = 0; // Highest pin to scan
|
||||||
|
int * ADCee::analogvals = NULL; // Array of analog values last captured
|
||||||
|
uint32_t * ADCee::analogchans = NULL; // Array of channel numbers to be scanned
|
||||||
|
// bool adc1configured = false;
|
||||||
|
ADC_TypeDef * * ADCee::adcchans = NULL; // Array to capture which ADC is each input channel on
|
||||||
|
|
||||||
|
int16_t ADCee::ADCmax()
|
||||||
|
{
|
||||||
return 4095;
|
return 4095;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,11 +394,34 @@ int ADCee::init(uint8_t pin) {
|
||||||
return -1024; // some silly value as error
|
return -1024; // some silly value as error
|
||||||
|
|
||||||
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
|
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
|
||||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC input channel
|
||||||
GPIO_TypeDef * gpioBase;
|
ADC_TypeDef *adc = (ADC_TypeDef *)pinmap_find_peripheral(stmpin, PinMap_ADC); // find which ADC this pin is on ADC1/2/3 etc.
|
||||||
|
int adcnum = 1;
|
||||||
|
// All variants have ADC1
|
||||||
|
if (adc == ADC1)
|
||||||
|
DIAG(F("ADCee::init(): found pin %d on ADC1"), pin);
|
||||||
|
// Checking for ADC2 and ADC3 being defined helps cater for more variants
|
||||||
|
#if defined(ADC2)
|
||||||
|
else if (adc == ADC2)
|
||||||
|
{
|
||||||
|
DIAG(F("ADCee::init(): found pin %d on ADC2"), pin);
|
||||||
|
adcnum = 2;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if defined(ADC3)
|
||||||
|
else if (adc == ADC3)
|
||||||
|
{
|
||||||
|
DIAG(F("ADCee::init(): found pin %d on ADC3"), pin);
|
||||||
|
adcnum = 3;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else DIAG(F("ADCee::init(): found pin %d on unknown ADC!"), pin);
|
||||||
|
|
||||||
// Port config - find which port we're on and power it up
|
// Port config - find which port we're on and power it up
|
||||||
switch(stmgpio) {
|
GPIO_TypeDef *gpioBase;
|
||||||
|
|
||||||
|
switch (stmgpio)
|
||||||
|
{
|
||||||
case 0x00:
|
case 0x00:
|
||||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
||||||
gpioBase = GPIOA;
|
gpioBase = GPIOA;
|
||||||
|
@ -278,6 +434,32 @@ int ADCee::init(uint8_t pin) {
|
||||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||||
gpioBase = GPIOC;
|
gpioBase = GPIOC;
|
||||||
break;
|
break;
|
||||||
|
case 0x03:
|
||||||
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //Power up PORTD
|
||||||
|
gpioBase = GPIOD;
|
||||||
|
break;
|
||||||
|
case 0x04:
|
||||||
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; //Power up PORTE
|
||||||
|
gpioBase = GPIOE;
|
||||||
|
break;
|
||||||
|
#if defined(GPIOF)
|
||||||
|
case 0x05:
|
||||||
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; //Power up PORTF
|
||||||
|
gpioBase = GPIOF;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#if defined(GPIOG)
|
||||||
|
case 0x06:
|
||||||
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOGEN; //Power up PORTG
|
||||||
|
gpioBase = GPIOG;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#if defined(GPIOH)
|
||||||
|
case 0x07:
|
||||||
|
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOHEN; //Power up PORTH
|
||||||
|
gpioBase = GPIOH;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
return -1023; // some silly value as error
|
return -1023; // some silly value as error
|
||||||
}
|
}
|
||||||
|
@ -293,31 +475,33 @@ int ADCee::init(uint8_t pin) {
|
||||||
if (adcchan > 18)
|
if (adcchan > 18)
|
||||||
return -1022; // silly value as error
|
return -1022; // silly value as error
|
||||||
if (adcchan < 10)
|
if (adcchan < 10)
|
||||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
adc->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||||
else
|
else
|
||||||
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
adc->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||||
|
|
||||||
// Read the inital ADC value for this analog input
|
// Read the inital ADC value for this analog input
|
||||||
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
|
adc->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||||
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
adc->CR2 |= ADC_CR2_SWSTART; //(1 << 30); // Start 1st conversion SWSTART
|
||||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
while(!(adc->SR & (1 << 1))); // Wait until conversion is complete
|
||||||
value = ADC1->DR; // Read value from register
|
value = adc->DR; // Read value from register
|
||||||
|
|
||||||
uint8_t id = pin - PNUM_ANALOG_BASE;
|
uint8_t id = pin - PNUM_ANALOG_BASE;
|
||||||
if (id > 15) { // today we have not enough bits in the mask to support more
|
// if (id > 15) { // today we have not enough bits in the mask to support more
|
||||||
return -1021;
|
// return -1021;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init.
|
if (analogvals == NULL) { // allocate analogvals, analogchans and adcchans if this is the first invocation of init
|
||||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||||
|
adcchans = (ADC_TypeDef **)calloc(NUM_ADC_INPUTS+1, sizeof(ADC_TypeDef));
|
||||||
}
|
}
|
||||||
analogvals[id] = value; // Store sampled value
|
analogvals[id] = value; // Store sampled value
|
||||||
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
||||||
|
adcchans[id] = adc; // Keep track of which ADC this channel is on
|
||||||
usedpins |= (1 << id); // This pin is now ready
|
usedpins |= (1 << id); // This pin is now ready
|
||||||
if (id > highestPin) highestPin = id; // Store our highest pin in use
|
if (id > highestPin) highestPin = id; // Store our highest pin in use
|
||||||
|
|
||||||
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
|
DIAG(F("ADCee::init(): value=%d, ADC%d: channel=%d, id=%d"), value, adcnum, adcchan, id);
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -344,13 +528,16 @@ void ADCee::scan() {
|
||||||
static uint8_t id = 0; // id and mask are the same thing but it is faster to
|
static uint8_t id = 0; // id and mask are the same thing but it is faster to
|
||||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||||
static bool waiting = false;
|
static bool waiting = false;
|
||||||
|
static ADC_TypeDef *adc;
|
||||||
|
|
||||||
if (waiting) {
|
adc = adcchans[id];
|
||||||
|
if (waiting)
|
||||||
|
{
|
||||||
// look if we have a result
|
// look if we have a result
|
||||||
if (!(ADC1->SR & (1 << 1)))
|
if (!(adc->SR & (1 << 1)))
|
||||||
return; // no result, continue to wait
|
return; // no result, continue to wait
|
||||||
// found value
|
// found value
|
||||||
analogvals[id] = ADC1->DR;
|
analogvals[id] = adc->DR;
|
||||||
// advance at least one track
|
// advance at least one track
|
||||||
#ifdef DEBUG_ADC
|
#ifdef DEBUG_ADC
|
||||||
if (id == 1) TrackManager::track[1]->setBrake(0);
|
if (id == 1) TrackManager::track[1]->setBrake(0);
|
||||||
|
@ -370,8 +557,9 @@ void ADCee::scan() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (mask & usedpins) {
|
if (mask & usedpins) {
|
||||||
// start new ADC aquire on id
|
// start new ADC aquire on id
|
||||||
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
|
adc = adcchans[id];
|
||||||
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
|
adc->SQR3 = analogchans[id]; // 1st conversion in regular sequence
|
||||||
|
adc->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||||
#ifdef DEBUG_ADC
|
#ifdef DEBUG_ADC
|
||||||
if (id == 1) TrackManager::track[1]->setBrake(1);
|
if (id == 1) TrackManager::track[1]->setBrake(1);
|
||||||
#endif
|
#endif
|
||||||
|
@ -392,19 +580,83 @@ void ADCee::scan() {
|
||||||
void ADCee::begin() {
|
void ADCee::begin() {
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
//ADC1 config sequence
|
//ADC1 config sequence
|
||||||
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
|
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
|
||||||
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
|
|
||||||
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
||||||
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
||||||
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||||
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||||
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||||
|
// Disable the DMA controller for ADC1
|
||||||
|
ADC1->CR2 &= ~ADC_CR2_DMA;
|
||||||
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
||||||
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||||
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||||
|
// Wait for ADC1 to become ready (calibration complete)
|
||||||
|
while (!(ADC1->CR2 & ADC_CR2_ADON)) {
|
||||||
|
}
|
||||||
|
#if defined(ADC2)
|
||||||
|
// Enable the ADC2 clock
|
||||||
|
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
|
||||||
|
|
||||||
|
// Initialize ADC2
|
||||||
|
ADC2->CR1 = 0; // Disable all channels
|
||||||
|
ADC2->CR2 = 0; // Clear CR2 register
|
||||||
|
|
||||||
|
ADC2->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||||
|
ADC2->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||||
|
ADC2->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||||
|
ADC2->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||||
|
ADC2->CR2 &= ~(1 << 1); //Single conversion
|
||||||
|
ADC2->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||||
|
ADC2->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
|
ADC2->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
|
ADC2->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
|
|
||||||
|
// Enable the ADC
|
||||||
|
ADC2->CR2 |= ADC_CR2_ADON;
|
||||||
|
|
||||||
|
// Wait for ADC2 to become ready (calibration complete)
|
||||||
|
while (!(ADC2->CR2 & ADC_CR2_ADON)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform ADC3 calibration (optional)
|
||||||
|
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||||
|
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||||
|
// }
|
||||||
|
#endif
|
||||||
|
#if defined(ADC3)
|
||||||
|
// Enable the ADC3 clock
|
||||||
|
RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
|
||||||
|
|
||||||
|
// Initialize ADC3
|
||||||
|
ADC3->CR1 = 0; // Disable all channels
|
||||||
|
ADC3->CR2 = 0; // Clear CR2 register
|
||||||
|
|
||||||
|
ADC3->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||||
|
ADC3->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||||
|
ADC3->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||||
|
ADC3->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||||
|
ADC3->CR2 &= ~(1 << 1); //Single conversion
|
||||||
|
ADC3->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||||
|
ADC3->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
|
ADC3->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
|
ADC3->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||||
|
|
||||||
|
// Enable the ADC
|
||||||
|
ADC3->CR2 |= ADC_CR2_ADON;
|
||||||
|
|
||||||
|
// Wait for ADC3 to become ready (calibration complete)
|
||||||
|
while (!(ADC3->CR2 & ADC_CR2_ADON)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform ADC3 calibration (optional)
|
||||||
|
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||||
|
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||||
|
// }
|
||||||
|
#endif
|
||||||
interrupts();
|
interrupts();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -39,6 +39,15 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
myDCCTimer.begin(interruptHandler, DCC_SIGNAL_TIME);
|
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) {
|
bool DCCTimer::isPWMPin(byte pin) {
|
||||||
//Teensy: digitalPinHasPWM, todo
|
//Teensy: digitalPinHasPWM, todo
|
||||||
(void) pin;
|
(void) pin;
|
||||||
|
@ -141,6 +150,11 @@ void DCCTimer::reset() {
|
||||||
SCB_AIRCR = 0x05FA0004;
|
SCB_AIRCR = 0x05FA0004;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||||
|
}
|
||||||
|
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||||
|
}
|
||||||
|
|
||||||
int16_t ADCee::ADCmax() {
|
int16_t ADCee::ADCmax() {
|
||||||
return 4095;
|
return 4095;
|
||||||
}
|
}
|
||||||
|
|
109
DCCWaveform.cpp
109
DCCWaveform.cpp
|
@ -106,6 +106,7 @@ void DCCWaveform::interruptHandler() {
|
||||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||||
isMainTrack = isMain;
|
isMainTrack = isMain;
|
||||||
packetPending = false;
|
packetPending = false;
|
||||||
|
reminderWindowOpen = false;
|
||||||
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
||||||
state = WAVE_START;
|
state = WAVE_START;
|
||||||
// The +1 below is to allow the preamble generator to create the stop bit
|
// The +1 below is to allow the preamble generator to create the stop bit
|
||||||
|
@ -115,7 +116,21 @@ DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||||
bits_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 push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
|
@ -123,13 +138,19 @@ void DCCWaveform::interrupt2() {
|
||||||
// calculate the next bit to be sent:
|
// calculate the next bit to be sent:
|
||||||
// set state WAVE_MID_1 for a 1=bit
|
// set state WAVE_MID_1 for a 1=bit
|
||||||
// or WAVE_HIGH_0 for a 0 bit.
|
// or WAVE_HIGH_0 for a 0 bit.
|
||||||
|
|
||||||
if (remainingPreambles > 0 ) {
|
if (remainingPreambles > 0 ) {
|
||||||
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
state=WAVE_MID_1; // switch state to trigger LOW on next interrupt
|
||||||
remainingPreambles--;
|
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.
|
// 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.
|
// Allow for checkAck and its called functions using 22 bytes more.
|
||||||
DCCTimer::updateMinimumFreeMemoryISR(22);
|
else DCCTimer::updateMinimumFreeMemoryISR(22);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,29 +169,14 @@ void DCCWaveform::interrupt2() {
|
||||||
if (bytes_sent >= transmitLength) {
|
if (bytes_sent >= transmitLength) {
|
||||||
// end of transmission buffer... repeat or switch to next message
|
// end of transmission buffer... repeat or switch to next message
|
||||||
bytes_sent = 0;
|
bytes_sent = 0;
|
||||||
|
// preamble for next packet will start...
|
||||||
remainingPreambles = requiredPreambles;
|
remainingPreambles = requiredPreambles;
|
||||||
|
|
||||||
if (transmitRepeats > 0) {
|
// set the railcom coundown to trigger half way
|
||||||
transmitRepeats--;
|
// through the first preamble bit.
|
||||||
}
|
// Note.. we are still sending the last packet bit
|
||||||
else if (packetPending) {
|
// and we then have to allow for the packet end bit
|
||||||
// Copy pending packet to transmit packet
|
if (isMainTrack && railcomActive) DCCTimer::startRailcomTimer(9);
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
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!)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,8 +199,43 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||||
packetPending = true;
|
packetPending = true;
|
||||||
clearResets();
|
clearResets();
|
||||||
}
|
}
|
||||||
bool DCCWaveform::getPacketPending() {
|
|
||||||
return packetPending;
|
bool DCCWaveform::isReminderWindowOpen() {
|
||||||
|
return reminderWindowOpen && ! packetPending;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCWaveform::promotePendingPacket() {
|
||||||
|
// fill the transmission packet from the pending packet
|
||||||
|
|
||||||
|
// Just keep going if repeating
|
||||||
|
if (transmitRepeats > 0) {
|
||||||
|
transmitRepeats--;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
transmitLength = pendingLength;
|
||||||
|
transmitRepeats = pendingRepeats;
|
||||||
|
packetPending = false;
|
||||||
|
clearResets();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// nothing to do, just send idles or resets
|
||||||
|
// Fortunately reset and idle packets are the same length
|
||||||
|
// 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!)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -253,7 +294,7 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||||
// The resets will be zero not only now but as well repeats packets into the future
|
// The resets will be zero not only now but as well repeats packets into the future
|
||||||
clearResets(repeats+1);
|
clearResets(repeats+1);
|
||||||
{
|
{
|
||||||
int ret;
|
int ret = 0;
|
||||||
do {
|
do {
|
||||||
if(isMainTrack) {
|
if(isMainTrack) {
|
||||||
if (rmtMainChannel != NULL)
|
if (rmtMainChannel != NULL)
|
||||||
|
@ -266,18 +307,24 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DCCWaveform::getPacketPending() {
|
bool DCCWaveform::isReminderWindowOpen() {
|
||||||
if(isMainTrack) {
|
if(isMainTrack) {
|
||||||
if (rmtMainChannel == NULL)
|
if (rmtMainChannel == NULL)
|
||||||
return true;
|
return false;
|
||||||
return rmtMainChannel->busy();
|
return !rmtMainChannel->busy();
|
||||||
} else {
|
} else {
|
||||||
if (rmtProgChannel == NULL)
|
if (rmtProgChannel == NULL)
|
||||||
return true;
|
return false;
|
||||||
return rmtProgChannel->busy();
|
return !rmtProgChannel->busy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void IRAM_ATTR DCCWaveform::loop() {
|
void IRAM_ATTR DCCWaveform::loop() {
|
||||||
DCCACK::checkAck(progTrack.getResets());
|
DCCACK::checkAck(progTrack.getResets());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool DCCWaveform::setRailcom(bool on, bool debug) {
|
||||||
|
// TODO... ESP32 railcom waveform
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
* © 2021 M Steve Todd
|
* © 2021 M Steve Todd
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2021 Harald Barth
|
* © 2020-2024 Harald Barth
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -33,14 +33,21 @@
|
||||||
|
|
||||||
|
|
||||||
// Number of preamble bits.
|
// Number of preamble bits.
|
||||||
const int PREAMBLE_BITS_MAIN = 16;
|
const byte PREAMBLE_BITS_MAIN = 16;
|
||||||
const int PREAMBLE_BITS_PROG = 22;
|
const byte PREAMBLE_BITS_PROG = 22;
|
||||||
const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum.
|
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
|
// The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic
|
||||||
// to the transform array.
|
// 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
|
// NOTE: static functions are used for the overall controller, then
|
||||||
// one instance is created for each track.
|
// one instance is created for each track.
|
||||||
|
@ -76,11 +83,15 @@ class DCCWaveform {
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||||
bool getPacketPending();
|
bool isReminderWindowOpen();
|
||||||
|
void promotePendingPacket();
|
||||||
|
static bool setRailcom(bool on, bool debug);
|
||||||
|
static bool isRailcom() {return railcomActive;}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifndef ARDUINO_ARCH_ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
volatile bool packetPending;
|
volatile bool packetPending;
|
||||||
|
volatile bool reminderWindowOpen;
|
||||||
volatile byte sentResetsSincePacket;
|
volatile byte sentResetsSincePacket;
|
||||||
#else
|
#else
|
||||||
volatile uint32_t resetPacketBase;
|
volatile uint32_t resetPacketBase;
|
||||||
|
@ -101,6 +112,9 @@ class DCCWaveform {
|
||||||
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||||
byte pendingLength;
|
byte pendingLength;
|
||||||
byte pendingRepeats;
|
byte pendingRepeats;
|
||||||
|
static volatile bool railcomActive; // switched on by user
|
||||||
|
static volatile bool railcomDebug; // switched on by user
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
static RMTChannel *rmtMainChannel;
|
static RMTChannel *rmtMainChannel;
|
||||||
static RMTChannel *rmtProgChannel;
|
static RMTChannel *rmtProgChannel;
|
||||||
|
|
|
@ -37,7 +37,9 @@
|
||||||
class Display : public DisplayInterface {
|
class Display : public DisplayInterface {
|
||||||
public:
|
public:
|
||||||
Display(DisplayDevice *deviceDriver);
|
Display(DisplayDevice *deviceDriver);
|
||||||
|
#if !defined (MAX_CHARACTER_ROWS)
|
||||||
static const int MAX_CHARACTER_ROWS = 8;
|
static const int MAX_CHARACTER_ROWS = 8;
|
||||||
|
#endif
|
||||||
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
|
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
|
||||||
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
|
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,9 @@
|
||||||
xxx; \
|
xxx; \
|
||||||
t->refresh();}
|
t->refresh();}
|
||||||
#else
|
#else
|
||||||
#define DISPLAY_START(xxx) {}
|
#define DISPLAY_START(xxx) { \
|
||||||
|
xxx; \
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#endif // LCD_Implementation_h
|
#endif // LCD_Implementation_h
|
||||||
|
|
747
EXRAIL2.cpp
747
EXRAIL2.cpp
File diff suppressed because it is too large
Load Diff
117
EXRAIL2.h
117
EXRAIL2.h
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2020-2022 Chris Harlow
|
* © 2020-2022 Chris Harlow
|
||||||
* © 2022 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -25,6 +25,7 @@
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
|
#include "Turntables.h"
|
||||||
|
|
||||||
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
||||||
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
||||||
|
@ -32,16 +33,19 @@
|
||||||
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
|
// or more OPCODE_PAD instructions with the subsequent parameters. This wastes a byte but makes
|
||||||
// searching easier as a parameter can never be confused with an opcode.
|
// searching easier as a parameter can never be confused with an opcode.
|
||||||
//
|
//
|
||||||
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,OPCODE_TOGGLE_TURNOUT,
|
||||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||||
OPCODE_RESERVE,OPCODE_FREE,
|
OPCODE_RESERVE,OPCODE_FREE,
|
||||||
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
|
OPCODE_AT,OPCODE_AFTER,
|
||||||
|
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
|
||||||
OPCODE_ATGTE,OPCODE_ATLT,
|
OPCODE_ATGTE,OPCODE_ATLT,
|
||||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
||||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||||
|
OPCODE_BLINK,
|
||||||
OPCODE_ENDIF,OPCODE_ELSE,
|
OPCODE_ENDIF,OPCODE_ELSE,
|
||||||
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
OPCODE_DELAY,OPCODE_DELAYMINS,OPCODE_DELAYMS,OPCODE_RANDWAIT,
|
||||||
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
OPCODE_FON,OPCODE_FOFF,OPCODE_XFON,OPCODE_XFOFF,
|
||||||
|
OPCODE_FTOGGLE,OPCODE_XFTOGGLE,
|
||||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||||
|
@ -49,20 +53,27 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
|
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
|
||||||
#endif
|
#endif
|
||||||
OPCODE_POM,
|
OPCODE_POM,
|
||||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
|
OPCODE_START,OPCODE_SETLOCO,OPCODE_SETFREQ,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||||
OPCODE_PRINT,OPCODE_DCCACTIVATE,
|
OPCODE_PRINT,OPCODE_DCCACTIVATE,OPCODE_ASPECT,
|
||||||
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
|
||||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||||
OPCODE_SET_TRACK,
|
OPCODE_SET_TRACK,OPCODE_SET_POWER,
|
||||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||||
OPCODE_ONCHANGE,
|
OPCODE_ONCHANGE,
|
||||||
OPCODE_ONCLOCKTIME,
|
OPCODE_ONCLOCKTIME,
|
||||||
OPCODE_ONTIME,
|
OPCODE_ONTIME,
|
||||||
|
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
|
||||||
|
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
|
||||||
|
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
|
||||||
|
OPCODE_ONOVERLOAD,
|
||||||
|
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||||
|
OPCODE_ROUTE_DISABLED,
|
||||||
|
OPCODE_STASH,OPCODE_CLEAR_STASH,OPCODE_CLEAR_ALL_STASH,OPCODE_PICKUP_STASH,
|
||||||
|
OPCODE_ONBUTTON,OPCODE_ONSENSOR,
|
||||||
// OPcodes below this point are skip-nesting IF operations
|
// OPcodes below this point are skip-nesting IF operations
|
||||||
// placed here so that they may be skipped as a group
|
// placed here so that they may be skipped as a group
|
||||||
// see skipIfBlock()
|
// see skipIfBlock()
|
||||||
|
@ -74,7 +85,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||||
OPCODE_IFRE,
|
OPCODE_IFRE,
|
||||||
OPCODE_IFLOCO
|
OPCODE_IFLOCO,
|
||||||
|
OPCODE_IFTTPOSITION
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensure thrunge_lcd is put last as there may be more than one display,
|
// Ensure thrunge_lcd is put last as there may be more than one display,
|
||||||
|
@ -84,11 +96,27 @@ enum thrunger: byte {
|
||||||
thrunge_serial,thrunge_parse,
|
thrunge_serial,thrunge_parse,
|
||||||
thrunge_serial1, thrunge_serial2, thrunge_serial3,
|
thrunge_serial1, thrunge_serial2, thrunge_serial3,
|
||||||
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
||||||
thrunge_lcn,
|
thrunge_lcn,thrunge_message,
|
||||||
thrunge_lcd, // Must be last!!
|
thrunge_lcd, // Must be last!!
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum BlinkState: byte {
|
||||||
|
not_blink_task,
|
||||||
|
blink_low, // blink task running with pin LOW
|
||||||
|
blink_high, // blink task running with pin high
|
||||||
|
at_timeout // ATTIMEOUT timed out flag
|
||||||
|
};
|
||||||
|
|
||||||
|
// Flag bits for compile time features.
|
||||||
|
static const byte FEATURE_SIGNAL= 0x80;
|
||||||
|
static const byte FEATURE_LCC = 0x40;
|
||||||
|
static const byte FEATURE_ROSTER= 0x20;
|
||||||
|
static const byte FEATURE_ROUTESTATE= 0x10;
|
||||||
|
static const byte FEATURE_STASH = 0x08;
|
||||||
|
static const byte FEATURE_BLINK = 0x04;
|
||||||
|
static const byte FEATURE_SENSOR = 0x02;
|
||||||
|
|
||||||
|
|
||||||
// Flag bits for status of hardware and TPL
|
// Flag bits for status of hardware and TPL
|
||||||
static const byte SECTION_FLAG = 0x80;
|
static const byte SECTION_FLAG = 0x80;
|
||||||
|
@ -108,13 +136,20 @@ enum thrunger: byte {
|
||||||
class LookList {
|
class LookList {
|
||||||
public:
|
public:
|
||||||
LookList(int16_t size);
|
LookList(int16_t size);
|
||||||
|
void chain(LookList* chainTo);
|
||||||
void add(int16_t lookup, int16_t result);
|
void add(int16_t lookup, int16_t result);
|
||||||
int16_t find(int16_t value);
|
int16_t find(int16_t value); // finds result value
|
||||||
|
int16_t findPosition(int16_t value); // finds index
|
||||||
|
int16_t size();
|
||||||
|
void stream(Print * _stream);
|
||||||
|
void handleEvent(const FSH* reason,int16_t id);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int16_t m_size;
|
int16_t m_size;
|
||||||
int16_t m_loaded;
|
int16_t m_loaded;
|
||||||
int16_t * m_lookupArray;
|
int16_t * m_lookupArray;
|
||||||
int16_t * m_resultArray;
|
int16_t * m_resultArray;
|
||||||
|
LookList* m_chain;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RMFT2 {
|
class RMFT2 {
|
||||||
|
@ -130,9 +165,13 @@ class LookList {
|
||||||
static void activateEvent(int16_t addr, bool active);
|
static void activateEvent(int16_t addr, bool active);
|
||||||
static void changeEvent(int16_t id, bool change);
|
static void changeEvent(int16_t id, bool change);
|
||||||
static void clockEvent(int16_t clocktime, bool change);
|
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 SERVO_SIGNAL_FLAG=0x4000;
|
||||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||||
|
static const int16_t DCCX_SIGNAL_FLAG=0x3000;
|
||||||
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
||||||
// Throttle Info Access functions built by exrail macros
|
// Throttle Info Access functions built by exrail macros
|
||||||
static const byte rosterNameCount;
|
static const byte rosterNameCount;
|
||||||
|
@ -144,6 +183,11 @@ class LookList {
|
||||||
static const FSH * getTurnoutDescription(int16_t id);
|
static const FSH * getTurnoutDescription(int16_t id);
|
||||||
static const FSH * getRosterName(int16_t id);
|
static const FSH * getRosterName(int16_t id);
|
||||||
static const FSH * getRosterFunctions(int16_t id);
|
static const FSH * getRosterFunctions(int16_t id);
|
||||||
|
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);
|
||||||
|
static bool readSensor(uint16_t sensorId);
|
||||||
|
static bool isSignal(int16_t id,char rag);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||||
|
@ -153,18 +197,19 @@ private:
|
||||||
static bool getFlag(VPIN id,byte mask);
|
static bool getFlag(VPIN id,byte mask);
|
||||||
static int16_t progtrackLocoId;
|
static int16_t progtrackLocoId;
|
||||||
static void doSignal(int16_t id,char rag);
|
static void doSignal(int16_t id,char rag);
|
||||||
static bool isSignal(int16_t id,char rag);
|
|
||||||
static int16_t getSignalSlot(int16_t id);
|
static int16_t getSignalSlot(int16_t id);
|
||||||
static void setTurnoutHiddenState(Turnout * t);
|
static void setTurnoutHiddenState(Turnout * t);
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
static void setTurntableHiddenState(Turntable * tto);
|
||||||
|
#endif
|
||||||
static LookList* LookListLoader(OPCODE op1,
|
static LookList* LookListLoader(OPCODE op1,
|
||||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||||
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
|
|
||||||
static uint16_t getOperand(int progCounter,byte n);
|
static uint16_t getOperand(int progCounter,byte n);
|
||||||
|
static void killBlinkOnVpin(VPIN pin);
|
||||||
static RMFT2 * loopTask;
|
static RMFT2 * loopTask;
|
||||||
static RMFT2 * pausingTask;
|
static RMFT2 * pausingTask;
|
||||||
void delayMe(long millisecs);
|
void delayMe(long millisecs);
|
||||||
void driveLoco(byte speedo);
|
void driveLoco(byte speedo);
|
||||||
bool readSensor(uint16_t sensorId);
|
|
||||||
bool skipIfBlock();
|
bool skipIfBlock();
|
||||||
bool readLoco();
|
bool readLoco();
|
||||||
void loop2();
|
void loop2();
|
||||||
|
@ -175,10 +220,11 @@ private:
|
||||||
uint16_t getOperand(byte n);
|
uint16_t getOperand(byte n);
|
||||||
|
|
||||||
static bool diag;
|
static bool diag;
|
||||||
static const HIGHFLASH byte RouteCode[];
|
static const HIGHFLASH3 byte RouteCode[];
|
||||||
static const HIGHFLASH int16_t SignalDefinitions[];
|
static const HIGHFLASH int16_t SignalDefinitions[];
|
||||||
static byte flags[MAX_FLAGS];
|
static byte flags[MAX_FLAGS];
|
||||||
static LookList * sequenceLookup;
|
static Print * LCCSerial;
|
||||||
|
static LookList * routeLookup;
|
||||||
static LookList * onThrowLookup;
|
static LookList * onThrowLookup;
|
||||||
static LookList * onCloseLookup;
|
static LookList * onCloseLookup;
|
||||||
static LookList * onActivateLookup;
|
static LookList * onActivateLookup;
|
||||||
|
@ -188,6 +234,20 @@ private:
|
||||||
static LookList * onGreenLookup;
|
static LookList * onGreenLookup;
|
||||||
static LookList * onChangeLookup;
|
static LookList * onChangeLookup;
|
||||||
static LookList * onClockLookup;
|
static LookList * onClockLookup;
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
static LookList * onRotateLookup;
|
||||||
|
#endif
|
||||||
|
static LookList * onOverloadLookup;
|
||||||
|
|
||||||
|
static const int countLCCLookup;
|
||||||
|
static int onLCCLookup[];
|
||||||
|
static const byte compileFeatures;
|
||||||
|
static void manageRouteState(uint16_t id, byte state);
|
||||||
|
static void manageRouteCaption(uint16_t id, const FSH* caption);
|
||||||
|
static byte * routeStateArray;
|
||||||
|
static const FSH ** routeCaptionArray;
|
||||||
|
static int16_t * stashArray;
|
||||||
|
static int16_t maxStashId;
|
||||||
|
|
||||||
// Local variables - exist for each instance/task
|
// Local variables - exist for each instance/task
|
||||||
RMFT2 *next; // loop chain
|
RMFT2 *next; // loop chain
|
||||||
|
@ -197,10 +257,10 @@ private:
|
||||||
union {
|
union {
|
||||||
unsigned long waitAfter; // Used by OPCODE_AFTER
|
unsigned long waitAfter; // Used by OPCODE_AFTER
|
||||||
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
|
unsigned long timeoutStart; // Used by OPCODE_ATTIMEOUT
|
||||||
|
VPIN blinkPin; // Used by blink tasks
|
||||||
};
|
};
|
||||||
bool timeoutFlag;
|
|
||||||
byte taskId;
|
byte taskId;
|
||||||
|
BlinkState blinkState; // includes AT_TIMEOUT flag.
|
||||||
uint16_t loco;
|
uint16_t loco;
|
||||||
bool forward;
|
bool forward;
|
||||||
bool invert;
|
bool invert;
|
||||||
|
@ -209,4 +269,27 @@ private:
|
||||||
byte stackDepth;
|
byte stackDepth;
|
||||||
int callStack[MAX_STACK_DEPTH];
|
int callStack[MAX_STACK_DEPTH];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||||
|
#define SKIPOP progCounter+=3
|
||||||
|
|
||||||
|
// IO_I2CDFPlayer commands and values
|
||||||
|
enum : uint8_t{
|
||||||
|
DF_PLAY = 0x0F,
|
||||||
|
DF_VOL = 0x06,
|
||||||
|
DF_FOLDER = 0x2B, // Not a DFPlayer command, used to set folder nr where audio file is
|
||||||
|
DF_REPEATPLAY = 0x08,
|
||||||
|
DF_STOPPLAY = 0x16,
|
||||||
|
DF_EQ = 0x07, // Set equaliser, require parameter NORMAL, POP, ROCK, JAZZ, CLASSIC or BASS
|
||||||
|
DF_RESET = 0x0C,
|
||||||
|
DF_DACON = 0x1A,
|
||||||
|
DF_SETAM = 0x2A, // Set audio mixer 1 or 2 for this DFPLayer
|
||||||
|
DF_NORMAL = 0x00, // Equalizer parameters
|
||||||
|
DF_POP = 0x01,
|
||||||
|
DF_ROCK = 0x02,
|
||||||
|
DF_JAZZ = 0x03,
|
||||||
|
DF_CLASSIC = 0x04,
|
||||||
|
DF_BASS = 0x05,
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||||
* © 2022 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
|
@ -27,19 +27,27 @@
|
||||||
#undef ACTIVATE
|
#undef ACTIVATE
|
||||||
#undef ACTIVATEL
|
#undef ACTIVATEL
|
||||||
#undef AFTER
|
#undef AFTER
|
||||||
|
#undef AFTEROVERLOAD
|
||||||
#undef ALIAS
|
#undef ALIAS
|
||||||
#undef AMBER
|
#undef AMBER
|
||||||
#undef ANOUT
|
#undef ANOUT
|
||||||
|
#undef ASPECT
|
||||||
#undef AT
|
#undef AT
|
||||||
#undef ATGTE
|
#undef ATGTE
|
||||||
#undef ATLT
|
#undef ATLT
|
||||||
#undef ATTIMEOUT
|
#undef ATTIMEOUT
|
||||||
#undef AUTOMATION
|
#undef AUTOMATION
|
||||||
#undef AUTOSTART
|
#undef AUTOSTART
|
||||||
|
#undef BLINK
|
||||||
#undef BROADCAST
|
#undef BROADCAST
|
||||||
#undef CALL
|
#undef CALL
|
||||||
|
#undef CLEAR_STASH
|
||||||
|
#undef CLEAR_ALL_STASH
|
||||||
#undef CLOSE
|
#undef CLOSE
|
||||||
|
#undef CONFIGURE_SERVO
|
||||||
#undef DCC_SIGNAL
|
#undef DCC_SIGNAL
|
||||||
|
#undef DCCX_SIGNAL
|
||||||
|
#undef DCC_TURNTABLE
|
||||||
#undef DEACTIVATE
|
#undef DEACTIVATE
|
||||||
#undef DEACTIVATEL
|
#undef DEACTIVATEL
|
||||||
#undef DELAY
|
#undef DELAY
|
||||||
|
@ -53,15 +61,18 @@
|
||||||
#undef ENDTASK
|
#undef ENDTASK
|
||||||
#undef ESTOP
|
#undef ESTOP
|
||||||
#undef EXRAIL
|
#undef EXRAIL
|
||||||
|
#undef EXTT_TURNTABLE
|
||||||
#undef FADE
|
#undef FADE
|
||||||
#undef FOFF
|
#undef FOFF
|
||||||
#undef FOLLOW
|
#undef FOLLOW
|
||||||
#undef FON
|
#undef FON
|
||||||
#undef FORGET
|
#undef FORGET
|
||||||
|
#undef FTOGGLE
|
||||||
#undef FREE
|
#undef FREE
|
||||||
#undef FWD
|
#undef FWD
|
||||||
#undef GREEN
|
#undef GREEN
|
||||||
#undef HAL
|
#undef HAL
|
||||||
|
#undef HAL_IGNORE_DEFAULTS
|
||||||
#undef IF
|
#undef IF
|
||||||
#undef IFAMBER
|
#undef IFAMBER
|
||||||
#undef IFCLOSED
|
#undef IFCLOSED
|
||||||
|
@ -75,30 +86,41 @@
|
||||||
#undef IFRESERVE
|
#undef IFRESERVE
|
||||||
#undef IFTHROWN
|
#undef IFTHROWN
|
||||||
#undef IFTIMEOUT
|
#undef IFTIMEOUT
|
||||||
|
#undef IFTTPOSITION
|
||||||
#undef IFRE
|
#undef IFRE
|
||||||
#undef INVERT_DIRECTION
|
#undef INVERT_DIRECTION
|
||||||
|
#undef JMRI_SENSOR
|
||||||
#undef JOIN
|
#undef JOIN
|
||||||
#undef KILLALL
|
#undef KILLALL
|
||||||
#undef LATCH
|
#undef LATCH
|
||||||
#undef LCD
|
#undef LCD
|
||||||
#undef SCREEN
|
#undef SCREEN
|
||||||
|
#undef LCC
|
||||||
|
#undef LCCX
|
||||||
#undef LCN
|
#undef LCN
|
||||||
#undef MOVETT
|
#undef MOVETT
|
||||||
|
#undef MESSAGE
|
||||||
#undef ONACTIVATE
|
#undef ONACTIVATE
|
||||||
#undef ONACTIVATEL
|
#undef ONACTIVATEL
|
||||||
#undef ONAMBER
|
#undef ONAMBER
|
||||||
#undef ONDEACTIVATE
|
#undef ONDEACTIVATE
|
||||||
#undef ONDEACTIVATEL
|
#undef ONDEACTIVATEL
|
||||||
#undef ONCLOSE
|
#undef ONCLOSE
|
||||||
|
#undef ONLCC
|
||||||
#undef ONTIME
|
#undef ONTIME
|
||||||
#undef ONCLOCKTIME
|
#undef ONCLOCKTIME
|
||||||
#undef ONCLOCKMINS
|
#undef ONCLOCKMINS
|
||||||
|
#undef ONOVERLOAD
|
||||||
#undef ONGREEN
|
#undef ONGREEN
|
||||||
#undef ONRED
|
#undef ONRED
|
||||||
|
#undef ONROTATE
|
||||||
|
#undef ONBUTTON
|
||||||
|
#undef ONSENSOR
|
||||||
#undef ONTHROW
|
#undef ONTHROW
|
||||||
#undef ONCHANGE
|
#undef ONCHANGE
|
||||||
#undef PARSE
|
#undef PARSE
|
||||||
#undef PAUSE
|
#undef PAUSE
|
||||||
|
#undef PICKUP_STASH
|
||||||
#undef PIN_TURNOUT
|
#undef PIN_TURNOUT
|
||||||
#undef PRINT
|
#undef PRINT
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
|
@ -114,7 +136,14 @@
|
||||||
#undef RETURN
|
#undef RETURN
|
||||||
#undef REV
|
#undef REV
|
||||||
#undef ROSTER
|
#undef ROSTER
|
||||||
|
#undef ROTATE
|
||||||
|
#undef ROTATE_DCC
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
|
#undef ROUTE_ACTIVE
|
||||||
|
#undef ROUTE_INACTIVE
|
||||||
|
#undef ROUTE_HIDDEN
|
||||||
|
#undef ROUTE_DISABLED
|
||||||
|
#undef ROUTE_CAPTION
|
||||||
#undef SENDLOCO
|
#undef SENDLOCO
|
||||||
#undef SEQUENCE
|
#undef SEQUENCE
|
||||||
#undef SERIAL
|
#undef SERIAL
|
||||||
|
@ -130,13 +159,20 @@
|
||||||
#undef SERVO_SIGNAL
|
#undef SERVO_SIGNAL
|
||||||
#undef SET
|
#undef SET
|
||||||
#undef SET_TRACK
|
#undef SET_TRACK
|
||||||
|
#undef SET_POWER
|
||||||
#undef SETLOCO
|
#undef SETLOCO
|
||||||
|
#undef SETFREQ
|
||||||
#undef SIGNAL
|
#undef SIGNAL
|
||||||
#undef SIGNALH
|
#undef SIGNALH
|
||||||
#undef SPEED
|
#undef SPEED
|
||||||
#undef START
|
#undef START
|
||||||
|
#undef STASH
|
||||||
|
#undef STEALTH
|
||||||
|
#undef STEALTH_GLOBAL
|
||||||
#undef STOP
|
#undef STOP
|
||||||
#undef THROW
|
#undef THROW
|
||||||
|
#undef TOGGLE_TURNOUT
|
||||||
|
#undef TT_ADDPOSITION
|
||||||
#undef TURNOUT
|
#undef TURNOUT
|
||||||
#undef TURNOUTL
|
#undef TURNOUTL
|
||||||
#undef UNJOIN
|
#undef UNJOIN
|
||||||
|
@ -144,27 +180,39 @@
|
||||||
#undef VIRTUAL_SIGNAL
|
#undef VIRTUAL_SIGNAL
|
||||||
#undef VIRTUAL_TURNOUT
|
#undef VIRTUAL_TURNOUT
|
||||||
#undef WAITFOR
|
#undef WAITFOR
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#undef WAITFORTT
|
||||||
|
#endif
|
||||||
#undef WITHROTTLE
|
#undef WITHROTTLE
|
||||||
#undef XFOFF
|
#undef XFOFF
|
||||||
#undef XFON
|
#undef XFON
|
||||||
|
#undef XFTOGGLE
|
||||||
|
|
||||||
#ifndef RMFT2_UNDEF_ONLY
|
#ifndef RMFT2_UNDEF_ONLY
|
||||||
#define ACTIVATE(addr,subaddr)
|
#define ACTIVATE(addr,subaddr)
|
||||||
#define ACTIVATEL(addr)
|
#define ACTIVATEL(addr)
|
||||||
#define AFTER(sensor_id)
|
#define AFTER(sensor_id)
|
||||||
|
#define AFTEROVERLOAD(track_id)
|
||||||
#define ALIAS(name,value...)
|
#define ALIAS(name,value...)
|
||||||
#define AMBER(signal_id)
|
#define AMBER(signal_id)
|
||||||
#define ANOUT(vpin,value,param1,param2)
|
#define ANOUT(vpin,value,param1,param2)
|
||||||
#define AT(sensor_id)
|
#define AT(sensor_id)
|
||||||
|
#define ASPECT(address,value)
|
||||||
#define ATGTE(sensor_id,value)
|
#define ATGTE(sensor_id,value)
|
||||||
#define ATLT(sensor_id,value)
|
#define ATLT(sensor_id,value)
|
||||||
#define ATTIMEOUT(sensor_id,timeout_ms)
|
#define ATTIMEOUT(sensor_id,timeout_ms)
|
||||||
#define AUTOMATION(id,description)
|
#define AUTOMATION(id,description)
|
||||||
#define AUTOSTART
|
#define AUTOSTART
|
||||||
|
#define BLINK(vpin,onDuty,offDuty)
|
||||||
#define BROADCAST(msg)
|
#define BROADCAST(msg)
|
||||||
#define CALL(route)
|
#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 DCC_SIGNAL(id,add,subaddr)
|
||||||
|
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect)
|
||||||
|
#define DCC_TURNTABLE(id,home,description)
|
||||||
#define DEACTIVATE(addr,subaddr)
|
#define DEACTIVATE(addr,subaddr)
|
||||||
#define DEACTIVATEL(addr)
|
#define DEACTIVATEL(addr)
|
||||||
#define DELAY(mindelay)
|
#define DELAY(mindelay)
|
||||||
|
@ -178,15 +226,18 @@
|
||||||
#define ENDTASK
|
#define ENDTASK
|
||||||
#define ESTOP
|
#define ESTOP
|
||||||
#define EXRAIL
|
#define EXRAIL
|
||||||
|
#define EXTT_TURNTABLE(id,vpin,home,description)
|
||||||
#define FADE(pin,value,ms)
|
#define FADE(pin,value,ms)
|
||||||
#define FOFF(func)
|
#define FOFF(func)
|
||||||
#define FOLLOW(route)
|
#define FOLLOW(route)
|
||||||
#define FON(func)
|
#define FON(func)
|
||||||
#define FORGET
|
#define FORGET
|
||||||
#define FREE(blockid)
|
#define FREE(blockid)
|
||||||
|
#define FTOGGLE(func)
|
||||||
#define FWD(speed)
|
#define FWD(speed)
|
||||||
#define GREEN(signal_id)
|
#define GREEN(signal_id)
|
||||||
#define HAL(haltype,params...)
|
#define HAL(haltype,params...)
|
||||||
|
#define HAL_IGNORE_DEFAULTS
|
||||||
#define IF(sensor_id)
|
#define IF(sensor_id)
|
||||||
#define IFAMBER(signal_id)
|
#define IFAMBER(signal_id)
|
||||||
#define IFCLOSED(turnout_id)
|
#define IFCLOSED(turnout_id)
|
||||||
|
@ -200,14 +251,19 @@
|
||||||
#define IFTHROWN(turnout_id)
|
#define IFTHROWN(turnout_id)
|
||||||
#define IFRESERVE(block)
|
#define IFRESERVE(block)
|
||||||
#define IFTIMEOUT
|
#define IFTIMEOUT
|
||||||
|
#define IFTTPOSITION(turntable_id,position)
|
||||||
#define IFRE(sensor_id,value)
|
#define IFRE(sensor_id,value)
|
||||||
#define INVERT_DIRECTION
|
#define INVERT_DIRECTION
|
||||||
|
#define JMRI_SENSOR(vpin,count...)
|
||||||
#define JOIN
|
#define JOIN
|
||||||
#define KILLALL
|
#define KILLALL
|
||||||
#define LATCH(sensor_id)
|
#define LATCH(sensor_id)
|
||||||
|
#define LCC(eventid)
|
||||||
|
#define LCCX(senderid,eventid)
|
||||||
#define LCD(row,msg)
|
#define LCD(row,msg)
|
||||||
#define SCREEN(display,row,msg)
|
#define SCREEN(display,row,msg)
|
||||||
#define LCN(msg)
|
#define LCN(msg)
|
||||||
|
#define MESSAGE(msg)
|
||||||
#define MOVETT(id,steps,activity)
|
#define MOVETT(id,steps,activity)
|
||||||
#define ONACTIVATE(addr,subaddr)
|
#define ONACTIVATE(addr,subaddr)
|
||||||
#define ONACTIVATEL(linear)
|
#define ONACTIVATEL(linear)
|
||||||
|
@ -215,17 +271,23 @@
|
||||||
#define ONTIME(value)
|
#define ONTIME(value)
|
||||||
#define ONCLOCKTIME(hours,mins)
|
#define ONCLOCKTIME(hours,mins)
|
||||||
#define ONCLOCKMINS(mins)
|
#define ONCLOCKMINS(mins)
|
||||||
|
#define ONOVERLOAD(track_id)
|
||||||
#define ONDEACTIVATE(addr,subaddr)
|
#define ONDEACTIVATE(addr,subaddr)
|
||||||
#define ONDEACTIVATEL(linear)
|
#define ONDEACTIVATEL(linear)
|
||||||
#define ONCLOSE(turnout_id)
|
#define ONCLOSE(turnout_id)
|
||||||
|
#define ONLCC(sender,event)
|
||||||
#define ONGREEN(signal_id)
|
#define ONGREEN(signal_id)
|
||||||
#define ONRED(signal_id)
|
#define ONRED(signal_id)
|
||||||
|
#define ONROTATE(turntable_id)
|
||||||
#define ONTHROW(turnout_id)
|
#define ONTHROW(turnout_id)
|
||||||
#define ONCHANGE(sensor_id)
|
#define ONCHANGE(sensor_id)
|
||||||
|
#define ONSENSOR(sensor_id)
|
||||||
|
#define ONBUTTON(sensor_id)
|
||||||
#define PAUSE
|
#define PAUSE
|
||||||
#define PIN_TURNOUT(id,pin,description...)
|
#define PIN_TURNOUT(id,pin,description...)
|
||||||
#define PRINT(msg)
|
#define PRINT(msg)
|
||||||
#define PARSE(msg)
|
#define PARSE(msg)
|
||||||
|
#define PICKUP_STASH(id)
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
#define POM(cv,value)
|
#define POM(cv,value)
|
||||||
#endif
|
#endif
|
||||||
|
@ -238,8 +300,15 @@
|
||||||
#define RESUME
|
#define RESUME
|
||||||
#define RETURN
|
#define RETURN
|
||||||
#define REV(speed)
|
#define REV(speed)
|
||||||
#define ROUTE(id,description)
|
#define ROTATE(turntable_id,position,activity)
|
||||||
|
#define ROTATE_DCC(turntable_id,position)
|
||||||
#define ROSTER(cab,name,funcmap...)
|
#define ROSTER(cab,name,funcmap...)
|
||||||
|
#define ROUTE(id,description)
|
||||||
|
#define ROUTE_ACTIVE(id)
|
||||||
|
#define ROUTE_INACTIVE(id)
|
||||||
|
#define ROUTE_HIDDEN(id)
|
||||||
|
#define ROUTE_DISABLED(id)
|
||||||
|
#define ROUTE_CAPTION(id,caption)
|
||||||
#define SENDLOCO(cab,route)
|
#define SENDLOCO(cab,route)
|
||||||
#define SEQUENCE(id)
|
#define SEQUENCE(id)
|
||||||
#define SERIAL(msg)
|
#define SERIAL(msg)
|
||||||
|
@ -255,13 +324,20 @@
|
||||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||||
#define SET(pin)
|
#define SET(pin)
|
||||||
#define SET_TRACK(track,mode)
|
#define SET_TRACK(track,mode)
|
||||||
|
#define SET_POWER(track,onoff)
|
||||||
#define SETLOCO(loco)
|
#define SETLOCO(loco)
|
||||||
|
#define SETFREQ(loco,freq)
|
||||||
#define SIGNAL(redpin,amberpin,greenpin)
|
#define SIGNAL(redpin,amberpin,greenpin)
|
||||||
#define SIGNALH(redpin,amberpin,greenpin)
|
#define SIGNALH(redpin,amberpin,greenpin)
|
||||||
#define SPEED(speed)
|
#define SPEED(speed)
|
||||||
#define START(route)
|
#define START(route)
|
||||||
|
#define STASH(id)
|
||||||
|
#define STEALTH(code...)
|
||||||
|
#define STEALTH_GLOBAL(code...)
|
||||||
#define STOP
|
#define STOP
|
||||||
#define THROW(id)
|
#define THROW(id)
|
||||||
|
#define TOGGLE_TURNOUT(id)
|
||||||
|
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
|
||||||
#define TURNOUT(id,addr,subaddr,description...)
|
#define TURNOUT(id,addr,subaddr,description...)
|
||||||
#define TURNOUTL(id,addr,description...)
|
#define TURNOUTL(id,addr,description...)
|
||||||
#define UNJOIN
|
#define UNJOIN
|
||||||
|
@ -269,7 +345,12 @@
|
||||||
#define VIRTUAL_SIGNAL(id)
|
#define VIRTUAL_SIGNAL(id)
|
||||||
#define VIRTUAL_TURNOUT(id,description...)
|
#define VIRTUAL_TURNOUT(id,description...)
|
||||||
#define WAITFOR(pin)
|
#define WAITFOR(pin)
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#define WAITFORTT(turntable_id)
|
||||||
|
#endif
|
||||||
#define WITHROTTLE(msg)
|
#define WITHROTTLE(msg)
|
||||||
#define XFOFF(cab,func)
|
#define XFOFF(cab,func)
|
||||||
#define XFON(cab,func)
|
#define XFON(cab,func)
|
||||||
|
#define XFTOGGLE(cab,func)
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
328
EXRAIL2Parser.cpp
Normal file
328
EXRAIL2Parser.cpp
Normal file
|
@ -0,0 +1,328 @@
|
||||||
|
/*
|
||||||
|
* © 2021 Neil McKechnie
|
||||||
|
* © 2021-2023 Harald Barth
|
||||||
|
* © 2020-2023 Chris Harlow
|
||||||
|
* © 2022-2023 Colin Murdoch
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// THIS file is an extension of the RMFT2 class
|
||||||
|
// normally found in EXRAIL2.cpp
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "defines.h"
|
||||||
|
#include "EXRAIL2.h"
|
||||||
|
#include "DCC.h"
|
||||||
|
#include "KeywordHasher.h"
|
||||||
|
|
||||||
|
// This filter intercepts <> commands to do the following:
|
||||||
|
// - Implement RMFT specific commands/diagnostics
|
||||||
|
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||||
|
|
||||||
|
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
|
||||||
|
(void)stream; // avoid compiler warning if we don't access this parameter
|
||||||
|
|
||||||
|
switch(opcode) {
|
||||||
|
|
||||||
|
case 'D':
|
||||||
|
if (p[0]=="EXRAIL"_hk) { // <D EXRAIL ON/OFF>
|
||||||
|
diag = paramCount==2 && (p[1]=="ON"_hk || p[1]==1);
|
||||||
|
opcode=0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '/': // New EXRAIL command
|
||||||
|
if (parseSlash(stream,paramCount,p)) 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;
|
||||||
|
|
||||||
|
if (paramCount==0) { //<L> LCC adapter introducing self
|
||||||
|
LCCSerial=stream; // now we know where to send events we raise
|
||||||
|
|
||||||
|
// loop through all possible sent events
|
||||||
|
for (int progCounter=0;; SKIPOP) {
|
||||||
|
byte opcode=GET_OPCODE;
|
||||||
|
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||||
|
if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
|
||||||
|
if (opcode==OPCODE_LCCX) { // long form LCC
|
||||||
|
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
|
||||||
|
getOperand(progCounter,1),
|
||||||
|
getOperand(progCounter,2),
|
||||||
|
getOperand(progCounter,3),
|
||||||
|
getOperand(progCounter,0)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
|
||||||
|
// we stream the hex events we wish to listen to
|
||||||
|
// and at the same time build the event index looku.
|
||||||
|
|
||||||
|
|
||||||
|
int eventIndex=0;
|
||||||
|
for (int progCounter=0;; SKIPOP) {
|
||||||
|
byte opcode=GET_OPCODE;
|
||||||
|
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||||
|
if (opcode==OPCODE_ONLCC) {
|
||||||
|
onLCCLookup[eventIndex]=progCounter; // TODO skip...
|
||||||
|
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
|
||||||
|
eventIndex,
|
||||||
|
getOperand(progCounter,1),
|
||||||
|
getOperand(progCounter,2),
|
||||||
|
getOperand(progCounter,3),
|
||||||
|
getOperand(progCounter,0)
|
||||||
|
);
|
||||||
|
eventIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
|
||||||
|
opcode=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
|
||||||
|
int16_t eventid=p[0];
|
||||||
|
bool reject = eventid<0 || eventid>=countLCCLookup;
|
||||||
|
if (!reject) {
|
||||||
|
startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
|
||||||
|
opcode=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'J': // throttle info commands
|
||||||
|
if (paramCount<1) return;
|
||||||
|
switch(p[0]) {
|
||||||
|
case "A"_hk: // <JA> returns automations/routes
|
||||||
|
if (paramCount==1) {// <JA>
|
||||||
|
StringFormatter::send(stream, F("<jA"));
|
||||||
|
routeLookup->stream(stream);
|
||||||
|
StringFormatter::send(stream, F(">\n"));
|
||||||
|
opcode=0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (paramCount==2) { // <JA id>
|
||||||
|
int16_t id=p[1];
|
||||||
|
StringFormatter::send(stream,F("<jA %d %c \"%S\">\n"),
|
||||||
|
id, getRouteType(id), getRouteDescription(id));
|
||||||
|
|
||||||
|
if (compileFeatures & FEATURE_ROUTESTATE) {
|
||||||
|
// Send any non-default button states or captions
|
||||||
|
int16_t statePos=routeLookup->findPosition(id);
|
||||||
|
if (statePos>=0) {
|
||||||
|
if (routeStateArray[statePos])
|
||||||
|
StringFormatter::send(stream,F("<jB %d %d>\n"), id, routeStateArray[statePos]);
|
||||||
|
if (routeCaptionArray[statePos])
|
||||||
|
StringFormatter::send(stream,F("<jB %d \"%S\">\n"), id,routeCaptionArray[statePos]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
opcode=0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "M"_hk:
|
||||||
|
// NOTE: we only need to handle valid calls here because
|
||||||
|
// DCCEXParser has to have code to handle the <J<> cases where
|
||||||
|
// exrail isnt involved anyway.
|
||||||
|
// This entire code block is compiled out if STASH macros not used
|
||||||
|
if (!(compileFeatures & FEATURE_STASH)) return;
|
||||||
|
if (paramCount==1) { // <JM>
|
||||||
|
StringFormatter::send(stream,F("<jM %d>\n"),maxStashId);
|
||||||
|
opcode=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (paramCount==2) { // <JM id>
|
||||||
|
if (p[1]<=0 || p[1]>maxStashId) break;
|
||||||
|
StringFormatter::send(stream,F("<jM %d %d>\n"),
|
||||||
|
p[1],stashArray[p[1]]);
|
||||||
|
opcode=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (paramCount==3) { // <JM id cab>
|
||||||
|
if (p[1]<=0 || p[1]>maxStashId) break;
|
||||||
|
stashArray[p[1]]=p[2];
|
||||||
|
opcode=0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: // other commands pass through
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||||
|
|
||||||
|
if (paramCount==0) { // STATUS
|
||||||
|
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
|
||||||
|
RMFT2 * task=loopTask;
|
||||||
|
while(task) {
|
||||||
|
if ((compileFeatures & FEATURE_BLINK)
|
||||||
|
&& (task->blinkState==blink_high || task->blinkState==blink_low)) {
|
||||||
|
StringFormatter::send(stream,F("\nID=%d,PC=%d,BLINK=%d"),
|
||||||
|
(int)(task->taskId),task->progCounter,task->blinkPin
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
|
||||||
|
(int)(task->taskId),task->progCounter,task->loco,
|
||||||
|
task->invert?'I':' ',
|
||||||
|
task->speedo,
|
||||||
|
task->forward?'F':'R'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
task=task->next;
|
||||||
|
if (task==loopTask) break;
|
||||||
|
}
|
||||||
|
// Now stream the flags
|
||||||
|
for (int id=0;id<MAX_FLAGS; id++) {
|
||||||
|
byte flag=flags[id];
|
||||||
|
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||||
|
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||||
|
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||||
|
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compileFeatures & FEATURE_SIGNAL) {
|
||||||
|
// do the signals
|
||||||
|
// flags[n] represents the state of the nth signal in the table
|
||||||
|
for (int sigslot=0;;sigslot++) {
|
||||||
|
int16_t sighandle=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||||
|
if (sighandle==0) break; // end of signal list
|
||||||
|
VPIN sigid = sighandle & SIGNAL_ID_MASK;
|
||||||
|
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||||
|
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||||
|
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||||
|
sigid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compileFeatures & FEATURE_STASH) {
|
||||||
|
for (int i=1;i<=maxStashId;i++) {
|
||||||
|
if (stashArray[i])
|
||||||
|
StringFormatter::send(stream,F("\nSTASH[%d] Loco=%d"),
|
||||||
|
i, stashArray[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StringFormatter::send(stream,F(" *>\n"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
switch (p[0]) {
|
||||||
|
case "PAUSE"_hk: // </ PAUSE>
|
||||||
|
if (paramCount!=1) return false;
|
||||||
|
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||||
|
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "RESUME"_hk: // </ RESUME>
|
||||||
|
if (paramCount!=1) return false;
|
||||||
|
pausingTask=NULL;
|
||||||
|
{
|
||||||
|
RMFT2 * task=loopTask;
|
||||||
|
while(task) {
|
||||||
|
if (task->loco) task->driveLoco(task->speedo);
|
||||||
|
task=task->next;
|
||||||
|
if (task==loopTask) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
|
||||||
|
case "START"_hk: // </ START [cab] route >
|
||||||
|
if (paramCount<2 || paramCount>3) return false;
|
||||||
|
{
|
||||||
|
int route=(paramCount==2) ? p[1] : p[2];
|
||||||
|
uint16_t cab=(paramCount==2)? 0 : p[1];
|
||||||
|
int pc=routeLookup->find(route);
|
||||||
|
if (pc<0) return false;
|
||||||
|
RMFT2* task=new RMFT2(pc);
|
||||||
|
task->loco=cab;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||||
|
if (p[0]=="KILL"_hk && p[1]=="ALL"_hk) {
|
||||||
|
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// all other / commands take 1 parameter
|
||||||
|
if (paramCount!=2 ) return false;
|
||||||
|
|
||||||
|
switch (p[0]) {
|
||||||
|
case "KILL"_hk: // Kill taskid|ALL
|
||||||
|
{
|
||||||
|
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||||
|
RMFT2 * task=loopTask;
|
||||||
|
while(task) {
|
||||||
|
if (task->taskId==p[1]) {
|
||||||
|
task->kill(F("KILL"));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
task=task->next;
|
||||||
|
if (task==loopTask) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
|
||||||
|
case "RESERVE"_hk: // force reserve a section
|
||||||
|
return setFlag(p[1],SECTION_FLAG);
|
||||||
|
|
||||||
|
case "FREE"_hk: // force free a section
|
||||||
|
return setFlag(p[1],0,SECTION_FLAG);
|
||||||
|
|
||||||
|
case "LATCH"_hk:
|
||||||
|
return setFlag(p[1], LATCH_FLAG);
|
||||||
|
|
||||||
|
case "UNLATCH"_hk:
|
||||||
|
return setFlag(p[1], 0, LATCH_FLAG);
|
||||||
|
|
||||||
|
case "RED"_hk:
|
||||||
|
doSignal(p[1],SIGNAL_RED);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "AMBER"_hk:
|
||||||
|
doSignal(p[1],SIGNAL_AMBER);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case "GREEN"_hk:
|
||||||
|
doSignal(p[1],SIGNAL_GREEN);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
260
EXRAILMacros.h
260
EXRAILMacros.h
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* © 2021 Neil McKechnie
|
* © 2021 Neil McKechnie
|
||||||
* © 2020-2022 Chris Harlow
|
* © 2020-2022 Chris Harlow
|
||||||
* © 2022 Colin Murdoch
|
* © 2022-2023 Colin Murdoch
|
||||||
* © 2023 Harald Barth
|
* © 2023 Harald Barth
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
|
@ -54,27 +54,171 @@
|
||||||
|
|
||||||
// helper macro for turnout descriptions, creates NULL for missing description
|
// helper macro for turnout descriptions, creates NULL for missing description
|
||||||
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
|
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
|
||||||
|
// helper macro for turntable descriptions, creates NULL for missing description
|
||||||
|
#define T_DESC(tid,pid,desc) if(turntableId==tid && positionId==pid) return ("" desc)[0]?F("" desc):NULL;
|
||||||
// helper macro for turnout description as HIDDEN
|
// helper macro for turnout description as HIDDEN
|
||||||
#define HIDDEN "\x01"
|
#define HIDDEN "\x01"
|
||||||
|
|
||||||
|
// PLAYSOUND is alias of ANOUT to make the user experience of a Conductor beter for
|
||||||
|
// playing sounds with IO_I2CDFPlayer
|
||||||
|
#define PLAYSOUND ANOUT
|
||||||
|
|
||||||
// helper macro to strip leading zeros off time inputs
|
// helper macro to strip leading zeros off time inputs
|
||||||
// (10#mins)%100)
|
// (10#mins)%100)
|
||||||
#define STRIP_ZERO(value) 10##value%100
|
#define STRIP_ZERO(value) 10##value%100
|
||||||
|
|
||||||
|
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||||
|
//const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||||
|
//const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||||
|
|
||||||
|
|
||||||
// Pass 1 Implements aliases
|
// Pass 1 Implements aliases
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef ALIAS
|
#undef ALIAS
|
||||||
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
|
#define ALIAS(name,value...) const int name= #value[0] ? value+0: -__COUNTER__ ;
|
||||||
|
#include "myAutomation.h"
|
||||||
|
|
||||||
|
// Pass 1d Detect sequence duplicates.
|
||||||
|
// This pass generates no runtime data or code
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef AUTOMATION
|
||||||
|
#define AUTOMATION(id, description) id,
|
||||||
|
#undef ROUTE
|
||||||
|
#define ROUTE(id, description) id,
|
||||||
|
#undef SEQUENCE
|
||||||
|
#define SEQUENCE(id) id,
|
||||||
|
constexpr int16_t compileTimeSequenceList[]={
|
||||||
|
#include "myAutomation.h"
|
||||||
|
0
|
||||||
|
};
|
||||||
|
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 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 int16_t pos ) {
|
||||||
|
return pos>=stuffSize? false :
|
||||||
|
compileTimeSequenceList[pos]==value
|
||||||
|
|| hasseq(value,pos+1)
|
||||||
|
|| hasdup(compileTimeSequenceList[pos],pos+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static_assert(!hasdup(compileTimeSequenceList[0],1),"Duplicate SEQUENCE/ROUTE/AUTOMATION detected");
|
||||||
|
|
||||||
|
//pass 1s static asserts to
|
||||||
|
// - check call and follows etc for existing sequence numbers
|
||||||
|
// - 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
|
||||||
|
#define FOLLOW(id) static_assert(hasseq(id),"Sequence not found");
|
||||||
|
#undef START
|
||||||
|
#define START(id) static_assert(hasseq(id),"Sequence not found");
|
||||||
|
#undef SENDLOCO
|
||||||
|
#define SENDLOCO(cab,id) static_assert(hasseq(id),"Sequence not found");
|
||||||
|
#undef LATCH
|
||||||
|
#define LATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef UNLATCH
|
||||||
|
#define UNLATCH(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef RESERVE
|
||||||
|
#define RESERVE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef FREE
|
||||||
|
#define FREE(id) static_assert(id>=0 && id<MAX_FLAGS,"Id out of valid range 0-255" );
|
||||||
|
#undef SPEED
|
||||||
|
#define SPEED(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
||||||
|
#undef FWD
|
||||||
|
#define FWD(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
||||||
|
#undef REV
|
||||||
|
#define REV(speed) static_assert(speed>=0 && speed<128,"Speed out of valid range 0-127");
|
||||||
|
|
||||||
|
#include "myAutomation.h"
|
||||||
|
|
||||||
|
// Pass 1g Implants STEALTH_GLOBAL in correct place
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef STEALTH_GLOBAL
|
||||||
|
#define STEALTH_GLOBAL(code...) code
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
|
|
||||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||||
|
// Also allows creating EXTurntable object
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef HAL
|
#undef HAL
|
||||||
#define HAL(haltype,params...) haltype::create(params);
|
#define HAL(haltype,params...) haltype::create(params);
|
||||||
void exrailHalSetup() {
|
#undef HAL_IGNORE_DEFAULTS
|
||||||
|
#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"
|
#include "myAutomation.h"
|
||||||
|
return ignore_defaults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass 1c detect compile time featurtes
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef SIGNAL
|
||||||
|
#define SIGNAL(redpin,amberpin,greenpin) | FEATURE_SIGNAL
|
||||||
|
#undef SIGNALH
|
||||||
|
#define SIGNALH(redpin,amberpin,greenpin) | FEATURE_SIGNAL
|
||||||
|
#undef SERVO_SIGNAL
|
||||||
|
#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
|
||||||
|
|
||||||
|
#undef LCC
|
||||||
|
#define LCC(eventid) | FEATURE_LCC
|
||||||
|
#undef LCCX
|
||||||
|
#define LCCX(senderid,eventid) | FEATURE_LCC
|
||||||
|
#undef ONLCC
|
||||||
|
#define ONLCC(senderid,eventid) | FEATURE_LCC
|
||||||
|
#undef ROUTE_ACTIVE
|
||||||
|
#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE
|
||||||
|
#undef ROUTE_INACTIVE
|
||||||
|
#define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE
|
||||||
|
#undef ROUTE_HIDDEN
|
||||||
|
#define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE
|
||||||
|
#undef ROUTE_DISABLED
|
||||||
|
#define ROUTE_DISABLED(id) | FEATURE_ROUTESTATE
|
||||||
|
#undef ROUTE_CAPTION
|
||||||
|
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
|
||||||
|
|
||||||
|
#undef CLEAR_STASH
|
||||||
|
#define CLEAR_STASH(id) | FEATURE_STASH
|
||||||
|
#undef CLEAR_ALL_STASH
|
||||||
|
#define CLEAR_ALL_STASH | FEATURE_STASH
|
||||||
|
#undef PICKUP_STASH
|
||||||
|
#define PICKUP_STASH(id) | FEATURE_STASH
|
||||||
|
#undef STASH
|
||||||
|
#define STASH(id) | FEATURE_STASH
|
||||||
|
#undef BLINK
|
||||||
|
#define BLINK(vpin,onDuty,offDuty) | FEATURE_BLINK
|
||||||
|
#undef ONBUTTON
|
||||||
|
#define ONBUTTON(vpin) | FEATURE_SENSOR
|
||||||
|
#undef ONSENSOR
|
||||||
|
#define ONSENSOR(vpin) | FEATURE_SENSOR
|
||||||
|
|
||||||
|
const byte RMFT2::compileFeatures = 0
|
||||||
|
#include "myAutomation.h"
|
||||||
|
;
|
||||||
|
|
||||||
// Pass 2 create throttle route list
|
// Pass 2 create throttle route list
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef ROUTE
|
#undef ROUTE
|
||||||
|
@ -121,6 +265,15 @@ const int StringMacroTracker1=__COUNTER__;
|
||||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||||
#undef LCN
|
#undef LCN
|
||||||
#define LCN(msg) THRUNGE(msg,thrunge_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) : {\
|
||||||
|
manageRouteCaption(id,F(caption));\
|
||||||
|
return;\
|
||||||
|
}
|
||||||
#undef SERIAL
|
#undef SERIAL
|
||||||
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
||||||
#undef SERIAL1
|
#undef SERIAL1
|
||||||
|
@ -153,6 +306,8 @@ const int StringMacroTracker1=__COUNTER__;
|
||||||
lcdid=id;\
|
lcdid=id;\
|
||||||
break;\
|
break;\
|
||||||
}
|
}
|
||||||
|
#undef STEALTH
|
||||||
|
#define STEALTH(code...) case (__COUNTER__ - StringMacroTracker1) : {code} return;
|
||||||
#undef WITHROTTLE
|
#undef WITHROTTLE
|
||||||
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
|
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
|
||||||
|
|
||||||
|
@ -189,6 +344,33 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass to get turntable descriptions (optional)
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef DCC_TURNTABLE
|
||||||
|
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
|
||||||
|
#undef EXTT_TURNTABLE
|
||||||
|
#define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
|
||||||
|
|
||||||
|
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
|
||||||
|
switch (turntableId) {
|
||||||
|
#include "myAutomation.h"
|
||||||
|
default:break;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass to get turntable position descriptions (optional)
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef TT_ADDPOSITION
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
// Pass 6: Roster IDs (count)
|
// Pass 6: Roster IDs (count)
|
||||||
#include "EXRAIL2MacroReset.h"
|
#include "EXRAIL2MacroReset.h"
|
||||||
#undef ROSTER
|
#undef ROSTER
|
||||||
|
@ -238,6 +420,8 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
|
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) vpin | RMFT2::SERVO_SIGNAL_FLAG,redval,amberval,greenval,
|
||||||
#undef DCC_SIGNAL
|
#undef DCC_SIGNAL
|
||||||
#define DCC_SIGNAL(id,addr,subaddr) id | RMFT2::DCC_SIGNAL_FLAG,addr,subaddr,0,
|
#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
|
#undef VIRTUAL_SIGNAL
|
||||||
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
||||||
|
|
||||||
|
@ -245,6 +429,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
0,0,0,0 };
|
0,0,0,0 };
|
||||||
|
|
||||||
|
// Pass 9 ONLCC counter and lookup array
|
||||||
|
#include "EXRAIL2MacroReset.h"
|
||||||
|
#undef ONLCC
|
||||||
|
#define ONLCC(sender,event) +1
|
||||||
|
|
||||||
|
const int RMFT2::countLCCLookup=0
|
||||||
|
#include "myAutomation.h"
|
||||||
|
;
|
||||||
|
int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||||
|
|
||||||
// Last Pass : create main routes table
|
// Last Pass : create main routes table
|
||||||
// Only undef the macros, not dummy them.
|
// Only undef the macros, not dummy them.
|
||||||
#define RMFT2_UNDEF_ONLY
|
#define RMFT2_UNDEF_ONLY
|
||||||
|
@ -258,24 +452,34 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
||||||
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
||||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||||
|
#define AFTEROVERLOAD(track_id) OPCODE_AFTEROVERLOAD,V(TRACK_NUMBER_##track_id),
|
||||||
#define ALIAS(name,value...)
|
#define ALIAS(name,value...)
|
||||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
#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 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 AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||||
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
|
#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),
|
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
|
#define ATTIMEOUT(sensor_id,timeout) OPCODE_ATTIMEOUT1,0,0,OPCODE_ATTIMEOUT2,V(sensor_id),OPCODE_PAD,V(timeout/100L),
|
||||||
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||||
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
#define AUTOSTART OPCODE_AUTOSTART,0,0,
|
||||||
|
#define BLINK(vpin,onDuty,offDuty) OPCODE_BLINK,V(vpin),OPCODE_PAD,V(onDuty),OPCODE_PAD,V(offDuty),
|
||||||
#define BROADCAST(msg) PRINT(msg)
|
#define BROADCAST(msg) PRINT(msg)
|
||||||
#define CALL(route) OPCODE_CALL,V(route),
|
#define CALL(route) OPCODE_CALL,V(route),
|
||||||
|
#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 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
|
||||||
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
|
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
|
||||||
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
|
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
|
||||||
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
|
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
|
||||||
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
||||||
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
#define DELAYRANDOM(mindelay,maxdelay) DELAY(mindelay) OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
||||||
#define DCC_SIGNAL(id,add,subaddr)
|
#define DCC_SIGNAL(id,add,subaddr)
|
||||||
|
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect)
|
||||||
#define DONE OPCODE_ENDTASK,0,0,
|
#define DONE OPCODE_ENDTASK,0,0,
|
||||||
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
|
#define DRIVE(analogpin) OPCODE_DRIVE,V(analogpin),
|
||||||
#define ELSE OPCODE_ELSE,0,0,
|
#define ELSE OPCODE_ELSE,0,0,
|
||||||
|
@ -284,15 +488,20 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define ENDTASK OPCODE_ENDTASK,0,0,
|
#define ENDTASK OPCODE_ENDTASK,0,0,
|
||||||
#define ESTOP OPCODE_SPEED,V(1),
|
#define ESTOP OPCODE_SPEED,V(1),
|
||||||
#define EXRAIL
|
#define EXRAIL
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
|
||||||
|
#endif
|
||||||
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
||||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||||
#define FON(func) OPCODE_FON,V(func),
|
#define FON(func) OPCODE_FON,V(func),
|
||||||
#define FORGET OPCODE_FORGET,0,0,
|
#define FORGET OPCODE_FORGET,0,0,
|
||||||
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
#define FREE(blockid) OPCODE_FREE,V(blockid),
|
||||||
|
#define FTOGGLE(func) OPCODE_FTOGGLE,V(func),
|
||||||
#define FWD(speed) OPCODE_FWD,V(speed),
|
#define FWD(speed) OPCODE_FWD,V(speed),
|
||||||
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
|
||||||
#define HAL(haltype,params...)
|
#define HAL(haltype,params...)
|
||||||
|
#define HAL_IGNORE_DEFAULTS
|
||||||
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
|
||||||
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
|
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
|
||||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||||
|
@ -306,29 +515,52 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#define IFTTPOSITION(id,position) OPCODE_IFTTPOSITION,V(id),OPCODE_PAD,V(position),
|
||||||
|
#endif
|
||||||
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
||||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||||
|
#define JMRI_SENSOR(vpin,count...)
|
||||||
#define JOIN OPCODE_JOIN,0,0,
|
#define JOIN OPCODE_JOIN,0,0,
|
||||||
#define KILLALL OPCODE_KILLALL,0,0,
|
#define KILLALL OPCODE_KILLALL,0,0,
|
||||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||||
|
#define LCC(eventid) OPCODE_LCC,V(eventid),
|
||||||
|
#define LCCX(sender,event) OPCODE_LCCX,V(event),\
|
||||||
|
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
||||||
|
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
|
||||||
|
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
|
||||||
#define LCD(id,msg) PRINT(msg)
|
#define LCD(id,msg) PRINT(msg)
|
||||||
#define SCREEN(display,id,msg) PRINT(msg)
|
#define SCREEN(display,id,msg) PRINT(msg)
|
||||||
|
#define STEALTH(code...) PRINT(dummy)
|
||||||
|
#define STEALTH_GLOBAL(code...)
|
||||||
#define LCN(msg) PRINT(msg)
|
#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 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 ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||||
|
#define ONLCC(sender,event) OPCODE_ONLCC,V(event),\
|
||||||
|
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
||||||
|
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
|
||||||
|
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
|
||||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
||||||
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
|
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
|
||||||
|
#define ONOVERLOAD(track_id) OPCODE_ONOVERLOAD,V(TRACK_NUMBER_##track_id),
|
||||||
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
||||||
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||||
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
||||||
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
|
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#define ONROTATE(id) OPCODE_ONROTATE,V(id),
|
||||||
|
#endif
|
||||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||||
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
|
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
|
||||||
|
#define ONSENSOR(sensor_id) OPCODE_ONSENSOR,V(sensor_id),
|
||||||
|
#define ONBUTTON(sensor_id) OPCODE_ONBUTTON,V(sensor_id),
|
||||||
#define PAUSE OPCODE_PAUSE,0,0,
|
#define PAUSE OPCODE_PAUSE,0,0,
|
||||||
|
#define PICKUP_STASH(id) OPCODE_PICKUP_STASH,V(id),
|
||||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||||
|
@ -345,7 +577,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define RETURN OPCODE_RETURN,0,0,
|
#define RETURN OPCODE_RETURN,0,0,
|
||||||
#define REV(speed) OPCODE_REV,V(speed),
|
#define REV(speed) OPCODE_REV,V(speed),
|
||||||
#define ROSTER(cabid,name,funcmap...)
|
#define ROSTER(cabid,name,funcmap...)
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#define ROTATE(id,position,activity) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(EXTurntable::activity),
|
||||||
|
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
|
||||||
|
#endif
|
||||||
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
||||||
|
#define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id),
|
||||||
|
#define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id),
|
||||||
|
#define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id),
|
||||||
|
#define ROUTE_DISABLED(id) OPCODE_ROUTE_DISABLED,V(id),
|
||||||
|
#define ROUTE_CAPTION(id,caption) PRINT(caption)
|
||||||
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
||||||
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
||||||
#define SERIAL(msg) PRINT(msg)
|
#define SERIAL(msg) PRINT(msg)
|
||||||
|
@ -361,13 +602,20 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||||
#define SET(pin) OPCODE_SET,V(pin),
|
#define SET(pin) OPCODE_SET,V(pin),
|
||||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||||
|
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
|
||||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||||
|
#define SETFREQ(loco,freq) OPCODE_SETLOCO,V(loco), OPCODE_SETFREQ,V(freq),
|
||||||
#define SIGNAL(redpin,amberpin,greenpin)
|
#define SIGNAL(redpin,amberpin,greenpin)
|
||||||
#define SIGNALH(redpin,amberpin,greenpin)
|
#define SIGNALH(redpin,amberpin,greenpin)
|
||||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||||
#define START(route) OPCODE_START,V(route),
|
#define START(route) OPCODE_START,V(route),
|
||||||
|
#define STASH(id) OPCODE_STASH,V(id),
|
||||||
#define STOP OPCODE_SPEED,V(0),
|
#define STOP OPCODE_SPEED,V(0),
|
||||||
#define THROW(id) OPCODE_THROW,V(id),
|
#define THROW(id) OPCODE_THROW,V(id),
|
||||||
|
#define TOGGLE_TURNOUT(id) OPCODE_TOGGLE_TURNOUT,V(id),
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#define TT_ADDPOSITION(id,position,value,angle,description...) OPCODE_TTADDPOSITION,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(value),OPCODE_PAD,V(angle),
|
||||||
|
#endif
|
||||||
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||||
#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
|
#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
|
||||||
#define UNJOIN OPCODE_UNJOIN,0,0,
|
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||||
|
@ -376,12 +624,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||||
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
|
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
|
||||||
#define WITHROTTLE(msg) PRINT(msg)
|
#define WITHROTTLE(msg) PRINT(msg)
|
||||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
#define WAITFORTT(turntable_id) OPCODE_WAITFORTT,V(turntable_id),
|
||||||
|
#endif
|
||||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||||
|
#define XFTOGGLE(cab,func) OPCODE_XFTOGGLE,V(cab),OPCODE_PAD,V(func),
|
||||||
|
|
||||||
// Build RouteCode
|
// Build RouteCode
|
||||||
const int StringMacroTracker2=__COUNTER__;
|
const int StringMacroTracker2=__COUNTER__;
|
||||||
const HIGHFLASH byte RMFT2::RouteCode[] = {
|
const HIGHFLASH3 byte RMFT2::RouteCode[] = {
|
||||||
#include "myAutomation.h"
|
#include "myAutomation.h"
|
||||||
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
|
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
|
||||||
|
|
||||||
|
|
104
EXRAILSensor.cpp
Normal file
104
EXRAILSensor.cpp
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* © 2024 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**********************************************************************
|
||||||
|
EXRAILSensor represents a sensor that should be monitored in order
|
||||||
|
to call an exrail ONBUTTON or ONCHANGE handler.
|
||||||
|
These are created at EXRAIL startup and thus need no delete or listing
|
||||||
|
capability.
|
||||||
|
The basic logic is similar to that found in the Sensor class
|
||||||
|
except that on the relevant change an EXRAIL thread is started.
|
||||||
|
**********************************************************************/
|
||||||
|
|
||||||
|
#include "EXRAILSensor.h"
|
||||||
|
#include "EXRAIL2.h"
|
||||||
|
|
||||||
|
void EXRAILSensor::checkAll() {
|
||||||
|
if (firstSensor == NULL) return; // No sensors to be scanned
|
||||||
|
if (readingSensor == NULL) {
|
||||||
|
// Not currently scanning sensor list
|
||||||
|
unsigned long thisTime = micros();
|
||||||
|
if (thisTime - lastReadCycle < cycleInterval) return;
|
||||||
|
// Required time has elapsed since last read cycle started,
|
||||||
|
// so initiate new scan through the sensor list
|
||||||
|
readingSensor = firstSensor;
|
||||||
|
lastReadCycle = thisTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop until either end of list is encountered or we pause for some reason
|
||||||
|
byte sensorCount = 0;
|
||||||
|
|
||||||
|
while (readingSensor != NULL) {
|
||||||
|
bool pause=readingSensor->check();
|
||||||
|
// Move to next sensor in list.
|
||||||
|
readingSensor = readingSensor->nextSensor;
|
||||||
|
// Currently process max of 16 sensors per entry.
|
||||||
|
// Performance measurements taken during development indicate that, with 128 sensors configured
|
||||||
|
// on 8x 16-pin MCP23017 GPIO expanders with polling (no change notification), all inputs can be read from the devices
|
||||||
|
// within 1.4ms (400Mhz I2C bus speed), and a full cycle of checking 128 sensors for changes takes under a millisecond.
|
||||||
|
if (pause || (++sensorCount)>=16) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EXRAILSensor::check() {
|
||||||
|
// check for debounced change in this sensor
|
||||||
|
inputState = RMFT2::readSensor(pin);
|
||||||
|
|
||||||
|
// Check if changed since last time, and process changes.
|
||||||
|
if (inputState == active) {// no change
|
||||||
|
latchDelay = minReadCount; // Reset counter
|
||||||
|
return false; // no change
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change detected ... has it stayed changed for long enough
|
||||||
|
if (latchDelay > 0) {
|
||||||
|
latchDelay--;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// change validated, act on it.
|
||||||
|
active = inputState;
|
||||||
|
latchDelay = minReadCount; // Reset debounce counter
|
||||||
|
if (onChange || active) {
|
||||||
|
new RMFT2(progCounter);
|
||||||
|
return true; // Don't check any more sensors on this entry
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXRAILSensor::EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange) {
|
||||||
|
// Add to the start of the list
|
||||||
|
//DIAG(F("ONthing vpin=%d at %d"), _pin, _progCounter);
|
||||||
|
nextSensor = firstSensor;
|
||||||
|
firstSensor = this;
|
||||||
|
|
||||||
|
pin=_pin;
|
||||||
|
progCounter=_progCounter;
|
||||||
|
onChange=_onChange;
|
||||||
|
|
||||||
|
IODevice::configureInput(pin, true);
|
||||||
|
active = IODevice::read(pin);
|
||||||
|
inputState = active;
|
||||||
|
latchDelay = minReadCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXRAILSensor *EXRAILSensor::firstSensor=NULL;
|
||||||
|
EXRAILSensor *EXRAILSensor::readingSensor=NULL;
|
||||||
|
unsigned long EXRAILSensor::lastReadCycle=0;
|
50
EXRAILSensor.h
Normal file
50
EXRAILSensor.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* © 2024 Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef EXRAILSensor_h
|
||||||
|
#define EXRAILSensor_h
|
||||||
|
#include "IODevice.h"
|
||||||
|
class EXRAILSensor {
|
||||||
|
static EXRAILSensor * firstSensor;
|
||||||
|
static EXRAILSensor * readingSensor;
|
||||||
|
static unsigned long lastReadCycle;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void checkAll();
|
||||||
|
|
||||||
|
EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange);
|
||||||
|
bool check();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs.
|
||||||
|
// should not be less than device scan cycle time.
|
||||||
|
static const byte minReadCount = 4; // number of additional scans before acting on change
|
||||||
|
// E.g. 1 means that a change is ignored for one scan and actioned on the next.
|
||||||
|
// Max value is 63
|
||||||
|
|
||||||
|
EXRAILSensor* nextSensor;
|
||||||
|
VPIN pin;
|
||||||
|
int progCounter;
|
||||||
|
bool active;
|
||||||
|
bool inputState;
|
||||||
|
bool onChange;
|
||||||
|
byte latchDelay;
|
||||||
|
};
|
||||||
|
#endif
|
|
@ -47,6 +47,10 @@ void EthernetInterface::setup()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef IP_ADDRESS
|
||||||
|
static IPAddress myIP(IP_ADDRESS);
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Aquire IP Address from DHCP and start server
|
* @brief Aquire IP Address from DHCP and start server
|
||||||
*
|
*
|
||||||
|
@ -60,7 +64,7 @@ EthernetInterface::EthernetInterface()
|
||||||
connected=false;
|
connected=false;
|
||||||
|
|
||||||
#ifdef IP_ADDRESS
|
#ifdef IP_ADDRESS
|
||||||
Ethernet.begin(mac, IP_ADDRESS);
|
Ethernet.begin(mac, myIP);
|
||||||
#else
|
#else
|
||||||
if (Ethernet.begin(mac) == 0)
|
if (Ethernet.begin(mac) == 0)
|
||||||
{
|
{
|
||||||
|
@ -136,7 +140,7 @@ bool EthernetInterface::checkLink() {
|
||||||
DIAG(F("Ethernet cable connected"));
|
DIAG(F("Ethernet cable connected"));
|
||||||
connected=true;
|
connected=true;
|
||||||
#ifdef IP_ADDRESS
|
#ifdef IP_ADDRESS
|
||||||
Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again
|
Ethernet.setLocalIP(myIP); // for static IP, set it again
|
||||||
#endif
|
#endif
|
||||||
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
||||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||||
|
|
3
FSH.h
3
FSH.h
|
@ -56,6 +56,7 @@ typedef __FlashStringHelper FSH;
|
||||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||||
// AVR_MEGA memory deliberately placed at end of link may need _far functions
|
// AVR_MEGA memory deliberately placed at end of link may need _far functions
|
||||||
#define HIGHFLASH __attribute__((section(".fini2")))
|
#define HIGHFLASH __attribute__((section(".fini2")))
|
||||||
|
#define HIGHFLASH3 __attribute__((section(".fini3")))
|
||||||
#define GETFARPTR(data) pgm_get_far_address(data)
|
#define GETFARPTR(data) pgm_get_far_address(data)
|
||||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
|
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
|
||||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
|
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
|
||||||
|
@ -63,6 +64,7 @@ typedef __FlashStringHelper FSH;
|
||||||
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
|
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
|
||||||
// as there is no progmem above 32kb anyway.
|
// as there is no progmem above 32kb anyway.
|
||||||
#define HIGHFLASH PROGMEM
|
#define HIGHFLASH PROGMEM
|
||||||
|
#define HIGHFLASH3 PROGMEM
|
||||||
#define GETFARPTR(data) ((uint32_t)(data))
|
#define GETFARPTR(data) ((uint32_t)(data))
|
||||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
|
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
|
||||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
|
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
|
||||||
|
@ -80,6 +82,7 @@ typedef __FlashStringHelper FSH;
|
||||||
typedef char FSH;
|
typedef char FSH;
|
||||||
#define FLASH
|
#define FLASH
|
||||||
#define HIGHFLASH
|
#define HIGHFLASH
|
||||||
|
#define HIGHFLASH3
|
||||||
#define GETFARPTR(data) ((uint32_t)(data))
|
#define GETFARPTR(data) ((uint32_t)(data))
|
||||||
#define GETFLASH(addr) (*(const byte *)(addr))
|
#define GETFLASH(addr) (*(const byte *)(addr))
|
||||||
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
|
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
#define GITHUB_SHA "3bddf4d"
|
#define GITHUB_SHA "devel-202406021945Z"
|
||||||
|
|
|
@ -48,12 +48,18 @@
|
||||||
static const FSH * guessI2CDeviceType(uint8_t address) {
|
static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||||
if (address >= 0x20 && address <= 0x26)
|
if (address >= 0x20 && address <= 0x26)
|
||||||
return F("GPIO Expander");
|
return F("GPIO Expander");
|
||||||
|
#ifdef FAST_CLOCK_I2C
|
||||||
|
else if (address == FAST_CLOCK_I2C)
|
||||||
|
return F("Fast Clock");
|
||||||
|
#endif
|
||||||
else if (address == 0x27)
|
else if (address == 0x27)
|
||||||
return F("GPIO Expander or LCD Display");
|
return F("GPIO Expander or LCD Display");
|
||||||
else if (address == 0x29)
|
else if (address == 0x29)
|
||||||
return F("Time-of-flight sensor");
|
return F("Time-of-flight sensor");
|
||||||
else if (address >= 0x3c && address <= 0x3d)
|
else if (address >= 0x3c && address <= 0x3d)
|
||||||
return F("OLED Display");
|
return F("OLED Display");
|
||||||
|
else if (address >= 0x48 && address <= 0x57) // SC16IS752x UART detection
|
||||||
|
return F("SC16IS75x UART");
|
||||||
else if (address >= 0x48 && address <= 0x4f)
|
else if (address >= 0x48 && address <= 0x4f)
|
||||||
return F("Analogue Inputs or PWM");
|
return F("Analogue Inputs or PWM");
|
||||||
else if (address >= 0x40 && address <= 0x4f)
|
else if (address >= 0x40 && address <= 0x4f)
|
||||||
|
@ -92,7 +98,7 @@ void I2CManagerClass::begin(void) {
|
||||||
// Probe and list devices. Use standard mode
|
// Probe and list devices. Use standard mode
|
||||||
// (clock speed 100kHz) for best device compatibility.
|
// (clock speed 100kHz) for best device compatibility.
|
||||||
_setClock(100000);
|
_setClock(100000);
|
||||||
unsigned long originalTimeout = _timeout;
|
uint32_t originalTimeout = _timeout;
|
||||||
setTimeout(1000); // use 1ms timeout for probes
|
setTimeout(1000); // use 1ms timeout for probes
|
||||||
|
|
||||||
#if defined(I2C_EXTENDED_ADDRESS)
|
#if defined(I2C_EXTENDED_ADDRESS)
|
||||||
|
|
|
@ -485,7 +485,7 @@ private:
|
||||||
// When retries are enabled, the timeout applies to each
|
// When retries are enabled, the timeout applies to each
|
||||||
// try, and failure from timeout does not get retried.
|
// try, and failure from timeout does not get retried.
|
||||||
// A value of 0 means disable timeout monitoring.
|
// A value of 0 means disable timeout monitoring.
|
||||||
unsigned long _timeout = 100000UL;
|
uint32_t _timeout = 100000UL;
|
||||||
|
|
||||||
// Finish off request block by waiting for completion and posting status.
|
// Finish off request block by waiting for completion and posting status.
|
||||||
uint8_t finishRB(I2CRB *rb, uint8_t status);
|
uint8_t finishRB(I2CRB *rb, uint8_t status);
|
||||||
|
@ -532,13 +532,14 @@ private:
|
||||||
uint8_t bytesToSend = 0;
|
uint8_t bytesToSend = 0;
|
||||||
uint8_t bytesToReceive = 0;
|
uint8_t bytesToReceive = 0;
|
||||||
uint8_t operation = 0;
|
uint8_t operation = 0;
|
||||||
unsigned long startTime = 0;
|
uint32_t startTime = 0;
|
||||||
uint8_t muxPhase = 0;
|
uint8_t muxPhase = 0;
|
||||||
uint8_t muxAddress = 0;
|
uint8_t muxAddress = 0;
|
||||||
uint8_t muxData[1];
|
uint8_t muxData[1];
|
||||||
uint8_t deviceAddress;
|
uint8_t deviceAddress;
|
||||||
const uint8_t *sendBuffer;
|
const uint8_t *sendBuffer;
|
||||||
uint8_t *receiveBuffer;
|
uint8_t *receiveBuffer;
|
||||||
|
uint8_t transactionState = 0;
|
||||||
|
|
||||||
volatile uint32_t pendingClockSpeed = 0;
|
volatile uint32_t pendingClockSpeed = 0;
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,10 @@ void I2CManagerClass::startTransaction() {
|
||||||
* Function to queue a request block and initiate operations.
|
* Function to queue a request block and initiate operations.
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||||
|
|
||||||
|
if (((req->operation & OPERATION_MASK) == OPERATION_READ) && req->readLen == 0)
|
||||||
|
return; // Ignore null read
|
||||||
|
|
||||||
req->status = I2C_STATUS_PENDING;
|
req->status = I2C_STATUS_PENDING;
|
||||||
req->nextRequest = NULL;
|
req->nextRequest = NULL;
|
||||||
ATOMIC_BLOCK() {
|
ATOMIC_BLOCK() {
|
||||||
|
@ -184,6 +188,7 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Initiate a write to an I2C device (non-blocking operation)
|
* Initiate a write to an I2C device (non-blocking operation)
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
@ -240,8 +245,8 @@ void I2CManagerClass::checkForTimeout() {
|
||||||
I2CRB *t = queueHead;
|
I2CRB *t = queueHead;
|
||||||
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
|
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
|
||||||
// Check for timeout
|
// Check for timeout
|
||||||
unsigned long elapsed = micros() - startTime;
|
int32_t elapsed = micros() - startTime;
|
||||||
if (elapsed > _timeout) {
|
if (elapsed > (int32_t)_timeout) {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
|
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
|
||||||
#endif
|
#endif
|
||||||
|
@ -300,12 +305,12 @@ void I2CManagerClass::handleInterrupt() {
|
||||||
|
|
||||||
// Check if current request has completed. If there's a current request
|
// Check if current request has completed. If there's a current request
|
||||||
// and state isn't active then state contains the completion status of the request.
|
// and state isn't active then state contains the completion status of the request.
|
||||||
if (state == I2C_STATE_COMPLETED && currentRequest != NULL) {
|
if (state == I2C_STATE_COMPLETED && currentRequest != NULL && currentRequest == queueHead) {
|
||||||
// Operation has completed.
|
// Operation has completed.
|
||||||
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|
||||||
|| currentRequest->operation & OPERATION_NORETRY)
|
|| currentRequest->operation & OPERATION_NORETRY)
|
||||||
{
|
{
|
||||||
// Status is OK, or has failed and retry count exceeded, or retries disabled.
|
// Status is OK, or has failed and retry count exceeded, or failed and retries disabled.
|
||||||
#if defined(I2C_EXTENDED_ADDRESS)
|
#if defined(I2C_EXTENDED_ADDRESS)
|
||||||
if (muxPhase == MuxPhase_PROLOG ) {
|
if (muxPhase == MuxPhase_PROLOG ) {
|
||||||
overallStatus = completionStatus;
|
overallStatus = completionStatus;
|
||||||
|
|
|
@ -26,27 +26,44 @@
|
||||||
#include "I2CManager.h"
|
#include "I2CManager.h"
|
||||||
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
|
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
|
||||||
|
|
||||||
//#include <avr/io.h>
|
|
||||||
//#include <avr/interrupt.h>
|
|
||||||
#include <wiring_private.h>
|
#include <wiring_private.h>
|
||||||
|
#include "stm32f4xx_hal_rcc.h"
|
||||||
|
|
||||||
/***************************************************************************
|
/*****************************************************************************
|
||||||
* Interrupt handler.
|
* STM32F4xx I2C native driver support
|
||||||
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
|
*
|
||||||
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
|
* Nucleo-64 and Nucleo-144 boards all use I2C1 as the default I2C peripheral
|
||||||
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
|
* Later we may wish to support other STM32 boards, allow use of an alternate
|
||||||
* bus on the SAMD architecture
|
* I2C bus, or more than one I2C bus on the STM32 architecture
|
||||||
***************************************************************************/
|
*****************************************************************************/
|
||||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
|
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
|
||||||
void I2C1_IRQHandler() {
|
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) \
|
||||||
|
|| defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) \
|
||||||
|
|| defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||||
|
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64
|
||||||
|
// and Nucleo-144 variants
|
||||||
|
I2C_TypeDef *s = I2C1;
|
||||||
|
|
||||||
|
// In init we will ask the STM32 HAL layer for the configured APB1 clock frequency in Hz
|
||||||
|
uint32_t APB1clk1; // Peripheral Input Clock speed in Hz.
|
||||||
|
uint32_t i2c_MHz; // Peripheral Input Clock speed in MHz.
|
||||||
|
|
||||||
|
// IRQ handler for I2C1, replacing the weak definition in the STM32 HAL
|
||||||
|
extern "C" void I2C1_EV_IRQHandler(void) {
|
||||||
I2CManager.handleInterrupt();
|
I2CManager.handleInterrupt();
|
||||||
}
|
}
|
||||||
|
extern "C" void I2C1_ER_IRQHandler(void) {
|
||||||
|
I2CManager.handleInterrupt();
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#warning STM32 board selected is not yet supported - so I2C1 peripheral is not defined
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
|
// Peripheral Input Clock speed in MHz.
|
||||||
I2C_TypeDef *s = I2C1;
|
// For STM32F446RE, the speed is 45MHz. Ideally, this should be determined
|
||||||
#define I2C_IRQn I2C1_EV_IRQn
|
// at run-time from the APB1 clock, as it can vary from STM32 family to family.
|
||||||
#define I2C_BUSFREQ 16
|
// #define I2C_PERIPH_CLK 45
|
||||||
|
|
||||||
// I2C SR1 Status Register #1 bit definitions for convenience
|
// I2C SR1 Status Register #1 bit definitions for convenience
|
||||||
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
|
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
|
||||||
|
@ -80,52 +97,65 @@ I2C_TypeDef *s = I2C1;
|
||||||
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
|
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
|
||||||
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
|
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
|
||||||
|
|
||||||
|
// States of the STM32 I2C driver state machine
|
||||||
|
enum {TS_IDLE,TS_START,TS_W_ADDR,TS_W_DATA,TS_W_STOP,TS_R_ADDR,TS_R_DATA,TS_R_STOP};
|
||||||
|
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
* Set I2C clock speed register. This should only be called outside of
|
* Set I2C clock speed register. This should only be called outside of
|
||||||
* a transmission. The I2CManagerClass::_setClock() function ensures
|
* a transmission. The I2CManagerClass::_setClock() function ensures
|
||||||
* that it is only called at the beginning of an I2C transaction.
|
* that it is only called at the beginning of an I2C transaction.
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||||
|
|
||||||
// Calculate a rise time appropriate to the requested bus speed
|
// Calculate a rise time appropriate to the requested bus speed
|
||||||
// Use 10x the rise time spec to enable integer divide of 62.5ns clock period
|
// Use 10x the rise time spec to enable integer divide of 50ns clock period
|
||||||
uint16_t t_rise;
|
uint16_t t_rise;
|
||||||
uint32_t ccr_freq;
|
|
||||||
if (i2cClockSpeed < 200000L) {
|
while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further
|
||||||
// i2cClockSpeed = 100000L;
|
// writes to CR1 while STOP is being executed!
|
||||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
|
||||||
}
|
// Disable the I2C device, as TRISE can only be programmed whilst disabled
|
||||||
else if (i2cClockSpeed < 800000L)
|
s->CR1 &= ~(I2C_CR1_PE); // Disable I2C
|
||||||
|
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||||
|
asm("nop"); // wait a bit... suggestion from online!
|
||||||
|
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||||
|
|
||||||
|
if (i2cClockSpeed > 100000UL)
|
||||||
{
|
{
|
||||||
i2cClockSpeed = 400000L;
|
// if (i2cClockSpeed > 400000L)
|
||||||
t_rise = 0x06; // (300ns / 62.5ns) + 1;
|
// i2cClockSpeed = 400000L;
|
||||||
// } else if (i2cClockSpeed < 1200000L) {
|
|
||||||
// i2cClockSpeed = 1000000L;
|
t_rise = 300; // nanoseconds
|
||||||
// t_rise = 120;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
i2cClockSpeed = 100000L;
|
// i2cClockSpeed = 100000L;
|
||||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
t_rise = 1000; // nanoseconds
|
||||||
}
|
}
|
||||||
|
// Configure the rise time register - max allowed tRISE is 1000ns,
|
||||||
// Enable the I2C master mode
|
// so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||||
s->CR1 &= ~(I2C_CR1_PE); // Enable I2C
|
s->TRISE = (t_rise * i2c_MHz / 1000) + 1;
|
||||||
// Software reset the I2C peripheral
|
|
||||||
// s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
|
||||||
// Release reset
|
|
||||||
// s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
|
||||||
|
|
||||||
// Calculate baudrate - using a rise time appropriate for the speed
|
|
||||||
ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2;
|
|
||||||
|
|
||||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||||
// Bit 14: Duty, fast mode duty cycle
|
// Bit 14: Duty, fast mode duty cycle (use 2:1)
|
||||||
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
|
// Bit 11-0: FREQR
|
||||||
s->CCR = (uint16_t)ccr_freq;
|
// if (i2cClockSpeed > 400000UL) {
|
||||||
|
// // In fast mode plus, I2C period is 3 * CCR * TPCLK1.
|
||||||
|
// // s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||||
|
// s->CCR = APB1clk1 / 3 / i2cClockSpeed; // Set I2C clockspeed to start!
|
||||||
|
// s->CCR |= 0xC000; // We need Fast Mode AND DUTY bits set
|
||||||
|
// } else {
|
||||||
|
// In standard and fast mode, I2C period is 2 * CCR * TPCLK1
|
||||||
|
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||||
|
s->CCR |= (APB1clk1 / 2 / i2cClockSpeed); // Set I2C clockspeed to start!
|
||||||
|
// s->CCR |= (i2c_MHz * 500 / (i2cClockSpeed / 1000)); // Set I2C clockspeed to start!
|
||||||
|
// if (i2cClockSpeed > 100000UL)
|
||||||
|
// s->CCR |= 0xC000; // We need Fast Mode bits set as well
|
||||||
|
// }
|
||||||
|
|
||||||
// Configure the rise time register
|
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||||
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
|
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||||
|
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||||
|
|
||||||
// Enable the I2C master mode
|
// Enable the I2C master mode
|
||||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||||
|
@ -136,32 +166,54 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
void I2CManagerClass::I2C_init()
|
void I2CManagerClass::I2C_init()
|
||||||
{
|
{
|
||||||
//Setting up the clocks
|
// Query the clockspeed from the STM32 HAL layer
|
||||||
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
|
APB1clk1 = HAL_RCC_GetPCLK1Freq();
|
||||||
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
|
i2c_MHz = APB1clk1 / 1000000UL;
|
||||||
|
// DIAG(F("I2C_init() peripheral clock speed is: %d"), i2c_MHz);
|
||||||
|
// Enable clocks
|
||||||
|
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;//(1 << 21); // Enable I2C CLOCK
|
||||||
|
// Reset the I2C1 peripheral to initial state
|
||||||
|
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST;
|
||||||
|
RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C1RST;
|
||||||
// Standard I2C pins are SCL on PB8 and SDA on PB9
|
// Standard I2C pins are SCL on PB8 and SDA on PB9
|
||||||
|
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
|
||||||
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
|
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
|
||||||
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
|
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
|
||||||
|
GPIOB->MODER &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all MODER bits for PB8 and PB9
|
||||||
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
|
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
|
||||||
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
|
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
|
||||||
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
|
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
|
||||||
|
GPIOB->PUPDR &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all PUPDR bits for PB8 and PB9
|
||||||
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
|
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
|
||||||
// Alt Function High register routing pins PB8 and PB9 for I2C1:
|
// Alt Function High register routing pins PB8 and PB9 for I2C1:
|
||||||
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
|
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
|
||||||
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
|
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
|
||||||
|
GPIOB->AFR[1] &= ~((15<<0) | (15<<4)); // Clear all AFR bits for PB8 on low nibble, PB9 on next nibble up
|
||||||
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
|
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
|
||||||
|
|
||||||
// Software reset the I2C peripheral
|
// Software reset the I2C peripheral
|
||||||
|
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1 peripheral
|
||||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||||
|
asm("nop"); // wait a bit... suggestion from online!
|
||||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||||
|
|
||||||
// Program the peripheral input clock in CR2 Register in order to generate correct timings
|
// Clear all bits in I2C CR2 register except reserved bits
|
||||||
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
|
s->CR2 &= 0xE000;
|
||||||
|
|
||||||
|
// Set I2C peripheral clock frequency
|
||||||
|
// s->CR2 |= I2C_PERIPH_CLK;
|
||||||
|
s->CR2 |= i2c_MHz;
|
||||||
|
// DIAG(F("I2C_init() peripheral clock is now: %d"), s->CR2);
|
||||||
|
|
||||||
|
// set own address to 00 - not used in master mode
|
||||||
|
I2C1->OAR1 = (1 << 14); // bit 14 should be kept at 1 according to the datasheet
|
||||||
|
|
||||||
#if defined(I2C_USE_INTERRUPTS)
|
#if defined(I2C_USE_INTERRUPTS)
|
||||||
// Setting NVIC
|
// Setting NVIC
|
||||||
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
|
NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities
|
||||||
NVIC_EnableIRQ(I2C_IRQn);
|
NVIC_EnableIRQ(I2C1_EV_IRQn);
|
||||||
|
NVIC_SetPriority(I2C1_ER_IRQn, 1); // Match default priorities
|
||||||
|
NVIC_EnableIRQ(I2C1_ER_IRQn);
|
||||||
|
|
||||||
// CR2 Interrupt Settings
|
// CR2 Interrupt Settings
|
||||||
// Bit 15-13: reserved
|
// Bit 15-13: reserved
|
||||||
|
@ -172,23 +224,28 @@ void I2CManagerClass::I2C_init()
|
||||||
// Bit 8: ITERREN - Error interrupt enable
|
// Bit 8: ITERREN - Error interrupt enable
|
||||||
// Bit 7-6: reserved
|
// Bit 7-6: reserved
|
||||||
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
|
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
|
||||||
// s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts
|
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
|
||||||
s->CR2 |= 0x0300; // Enable Event and Error interrupts
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// DIAG(F("I2C_init() setting initial I2C clock to 100KHz"));
|
||||||
// Calculate baudrate and set default rate for now
|
// Calculate baudrate and set default rate for now
|
||||||
// Configure the Clock Control Register for 100KHz SCL frequency
|
// Configure the Clock Control Register for 100KHz SCL frequency
|
||||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||||
// Bit 14: Duty, fast mode duty cycle
|
// Bit 14: Duty, fast mode duty cycle
|
||||||
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
|
// Bit 11-0: so CCR divisor would be clk / 2 / 100000 (where clk is in Hz)
|
||||||
s->CCR = 0x0050;
|
// s->CCR = I2C_PERIPH_CLK * 5;
|
||||||
|
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||||
|
s->CCR |= (APB1clk1 / 2 / 100000UL); // Set a default of 100KHz I2C clockspeed to start!
|
||||||
|
|
||||||
// Configure the rise time register - max allowed in 1000ns
|
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||||
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
|
s->TRISE = (1000 * i2c_MHz / 1000) + 1;
|
||||||
|
|
||||||
|
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||||
|
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||||
|
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||||
|
|
||||||
// Enable the I2C master mode
|
// Enable the I2C master mode
|
||||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||||
// Setting bus idle mode and wait for sync
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
|
@ -198,42 +255,23 @@ void I2CManagerClass::I2C_sendStart() {
|
||||||
|
|
||||||
// Set counters here in case this is a retry.
|
// Set counters here in case this is a retry.
|
||||||
rxCount = txCount = 0;
|
rxCount = txCount = 0;
|
||||||
uint8_t temp;
|
|
||||||
|
|
||||||
// On a single-master I2C bus, the start bit won't be sent until the bus
|
// On a single-master I2C bus, the start bit won't be sent until the bus
|
||||||
// state goes to IDLE so we can request it without waiting. On a
|
// state goes to IDLE so we can request it without waiting. On a
|
||||||
// multi-master bus, the bus may be BUSY under control of another master,
|
// multi-master bus, the bus may be BUSY under control of another master,
|
||||||
// in which case we can avoid some arbitration failures by waiting until
|
// in which case we can avoid some arbitration failures by waiting until
|
||||||
// the bus state is IDLE. We don't do that here.
|
// the bus state is IDLE. We don't do that here.
|
||||||
|
//while (s->SR2 & I2C_SR2_BUSY) {}
|
||||||
|
|
||||||
// If anything to send, initiate write. Otherwise initiate read.
|
// Check there's no STOP still in progress. If we OR the START bit into CR1
|
||||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
// and the STOP bit is already set, we could output multiple STOP conditions.
|
||||||
{
|
while (s->CR1 & I2C_CR1_STOP) {} // Wait for STOP bit to reset
|
||||||
// Send start for read operation
|
|
||||||
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
|
s->CR2 |= (I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable interrupts
|
||||||
s->CR1 |= I2C_CR1_START; // Generate START
|
s->CR2 &= ~I2C_CR2_ITBUFEN; // Don't enable buffer interupts yet.
|
||||||
// Send address with read flag (1) or'd in
|
s->CR1 &= ~I2C_CR1_POS; // Clear the POS bit
|
||||||
s->DR = (deviceAddress << 1) | 1; // send the address
|
s->CR1 |= (I2C_CR1_ACK | I2C_CR1_START); // Enable the ACK and generate START
|
||||||
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
|
transactionState = TS_START;
|
||||||
// Special case for 1 byte reads!
|
|
||||||
if (bytesToReceive == 1)
|
|
||||||
{
|
|
||||||
s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit
|
|
||||||
temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
|
|
||||||
s->CR1 |= I2C_CR1_STOP; // Stop I2C
|
|
||||||
}
|
|
||||||
else
|
|
||||||
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Send start for write operation
|
|
||||||
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
|
|
||||||
s->CR1 |= I2C_CR1_START; // Generate START
|
|
||||||
// Send address with write flag (0) or'd in
|
|
||||||
s->DR = (deviceAddress << 1) | 0; // send the address
|
|
||||||
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
|
|
||||||
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
|
@ -252,9 +290,11 @@ void I2CManagerClass::I2C_close() {
|
||||||
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
|
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
|
||||||
// Should never happen, but wait for up to 500us only.
|
// Should never happen, but wait for up to 500us only.
|
||||||
unsigned long startTime = micros();
|
unsigned long startTime = micros();
|
||||||
while ((s->CR1 && I2C_CR1_PE) != 0) {
|
while ((s->CR1 & I2C_CR1_PE) != 0) {
|
||||||
if (micros() - startTime >= 500UL) break;
|
if ((int32_t)(micros() - startTime) >= 500) break;
|
||||||
}
|
}
|
||||||
|
NVIC_DisableIRQ(I2C1_EV_IRQn);
|
||||||
|
NVIC_DisableIRQ(I2C1_ER_IRQn);
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************************************************************************
|
/***************************************************************************
|
||||||
|
@ -263,50 +303,217 @@ void I2CManagerClass::I2C_close() {
|
||||||
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
void I2CManagerClass::I2C_handleInterrupt() {
|
void I2CManagerClass::I2C_handleInterrupt() {
|
||||||
|
volatile uint16_t temp_sr1, temp_sr2;
|
||||||
|
|
||||||
if (s->SR1 && I2C_SR1_ARLO) {
|
temp_sr1 = s->SR1;
|
||||||
// Arbitration lost, restart
|
|
||||||
I2C_sendStart(); // Reinitiate request
|
// Check for errors first
|
||||||
} else if (s->SR1 && I2C_SR1_BERR) {
|
if (temp_sr1 & (I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR)) {
|
||||||
// Bus error
|
// Check which error flag is set
|
||||||
completionStatus = I2C_STATUS_BUS_ERROR;
|
if (temp_sr1 & I2C_SR1_AF)
|
||||||
state = I2C_STATE_COMPLETED;
|
{
|
||||||
} else if (s->SR1 && I2C_SR1_TXE) {
|
s->SR1 &= ~(I2C_SR1_AF); // Clear AF
|
||||||
// Master write completed
|
I2C_sendStop(); // Clear the bus
|
||||||
if (s->SR1 && (1<<10)) {
|
transactionState = TS_IDLE;
|
||||||
// Nacked, send stop.
|
|
||||||
I2C_sendStop();
|
|
||||||
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||||
state = I2C_STATE_COMPLETED;
|
state = I2C_STATE_COMPLETED;
|
||||||
} else if (bytesToSend) {
|
}
|
||||||
// Acked, so send next byte
|
else if (temp_sr1 & I2C_SR1_ARLO)
|
||||||
s->DR = sendBuffer[txCount++];
|
{
|
||||||
bytesToSend--;
|
// Arbitration lost, restart
|
||||||
} else if (bytesToReceive) {
|
s->SR1 &= ~(I2C_SR1_ARLO); // Clear ARLO
|
||||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
I2C_sendStart(); // Reinitiate request
|
||||||
// s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
|
transactionState = TS_START;
|
||||||
} else {
|
}
|
||||||
// Check both TxE/BTF == 1 before generating stop
|
else if (temp_sr1 & I2C_SR1_BERR)
|
||||||
while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE
|
{
|
||||||
while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF
|
// Bus error
|
||||||
// No more data to send/receive. Initiate a STOP condition and finish
|
s->SR1 &= ~(I2C_SR1_BERR); // Clear BERR
|
||||||
I2C_sendStop();
|
I2C_sendStop(); // Clear the bus
|
||||||
|
transactionState = TS_IDLE;
|
||||||
|
completionStatus = I2C_STATUS_BUS_ERROR;
|
||||||
state = I2C_STATE_COMPLETED;
|
state = I2C_STATE_COMPLETED;
|
||||||
}
|
}
|
||||||
} else if (s->SR1 && I2C_SR1_RXNE) {
|
}
|
||||||
// Master read completed without errors
|
else {
|
||||||
if (bytesToReceive == 1) {
|
// No error flags, so process event according to current state.
|
||||||
// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
|
switch (transactionState) {
|
||||||
I2C_sendStop(); // send stop
|
case TS_START:
|
||||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
if (temp_sr1 & I2C_SR1_SB) {
|
||||||
bytesToReceive = 0;
|
// Event EV5
|
||||||
|
// Start bit has been sent successfully and we have the bus.
|
||||||
|
// If anything to send, initiate write. Otherwise initiate read.
|
||||||
|
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) {
|
||||||
|
// Send address with read flag (1) or'd in
|
||||||
|
s->DR = (deviceAddress << 1) | 1; // send the address
|
||||||
|
transactionState = TS_R_ADDR;
|
||||||
|
} else {
|
||||||
|
// Send address with write flag (0) or'd in
|
||||||
|
s->DR = (deviceAddress << 1) | 0; // send the address
|
||||||
|
transactionState = TS_W_ADDR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// SB bit is cleared by writing to DR (already done).
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_W_ADDR:
|
||||||
|
if (temp_sr1 & I2C_SR1_ADDR) {
|
||||||
|
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||||
|
// Event EV6
|
||||||
|
// Address sent successfully, device has ack'd in response.
|
||||||
|
if (!bytesToSend) {
|
||||||
|
I2C_sendStop();
|
||||||
|
transactionState = TS_IDLE;
|
||||||
|
completionStatus = I2C_STATUS_OK;
|
||||||
state = I2C_STATE_COMPLETED;
|
state = I2C_STATE_COMPLETED;
|
||||||
} else if (bytesToReceive) {
|
} else {
|
||||||
// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
|
// Put one byte into DR to load shift register.
|
||||||
|
s->DR = sendBuffer[txCount++];
|
||||||
|
bytesToSend--;
|
||||||
|
if (bytesToSend) {
|
||||||
|
// Put another byte to load DR
|
||||||
|
s->DR = sendBuffer[txCount++];
|
||||||
|
bytesToSend--;
|
||||||
|
}
|
||||||
|
if (!bytesToSend) {
|
||||||
|
// No more bytes to send.
|
||||||
|
// The TXE interrupt occurs when the DR is empty, and the BTF interrupt
|
||||||
|
// occurs when the shift register is also empty (one character later).
|
||||||
|
// To avoid repeated TXE interrupts during this time, we disable TXE interrupt.
|
||||||
|
s->CR2 &= ~I2C_CR2_ITBUFEN; // Wait for BTF interrupt, disable TXE interrupt
|
||||||
|
transactionState = TS_W_STOP;
|
||||||
|
} else {
|
||||||
|
// More data remaining to send after this interrupt, enable TXE interrupt.
|
||||||
|
s->CR2 |= I2C_CR2_ITBUFEN;
|
||||||
|
transactionState = TS_W_DATA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_W_DATA:
|
||||||
|
if (temp_sr1 & I2C_SR1_TXE) {
|
||||||
|
// Event EV8_1/EV8
|
||||||
|
// Transmitter empty, write a byte to it.
|
||||||
|
if (bytesToSend) {
|
||||||
|
s->DR = sendBuffer[txCount++];
|
||||||
|
bytesToSend--;
|
||||||
|
if (!bytesToSend) {
|
||||||
|
s->CR2 &= ~I2C_CR2_ITBUFEN; // Disable TXE interrupt
|
||||||
|
transactionState = TS_W_STOP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_W_STOP:
|
||||||
|
if (temp_sr1 & I2C_SR1_BTF) {
|
||||||
|
// Event EV8_2
|
||||||
|
// Done, last character sent. Anything to receive?
|
||||||
|
if (bytesToReceive) {
|
||||||
|
I2C_sendStart();
|
||||||
|
// NOTE: Three redundant BTF interrupts take place between the
|
||||||
|
// first BTF interrupt and the START interrupt. I've tried all sorts
|
||||||
|
// of ways to eliminate them, and the only thing that worked for
|
||||||
|
// me was to loop until the BTF bit becomes reset. Either way,
|
||||||
|
// it's a waste of processor time. Anyone got a solution?
|
||||||
|
//while (s->SR1 && I2C_SR1_BTF) {}
|
||||||
|
transactionState = TS_START;
|
||||||
|
} else {
|
||||||
|
I2C_sendStop();
|
||||||
|
transactionState = TS_IDLE;
|
||||||
|
completionStatus = I2C_STATUS_OK;
|
||||||
|
state = I2C_STATE_COMPLETED;
|
||||||
|
}
|
||||||
|
s->SR1 &= I2C_SR1_BTF; // Clear BTF interrupt
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_R_ADDR:
|
||||||
|
if (temp_sr1 & I2C_SR1_ADDR) {
|
||||||
|
// Event EV6
|
||||||
|
// Address sent for receive.
|
||||||
|
// The next bit is different depending on whether there are
|
||||||
|
// 1 byte, 2 bytes or >2 bytes to be received, in accordance with the
|
||||||
|
// Programmers Reference RM0390.
|
||||||
|
if (bytesToReceive == 1) {
|
||||||
|
// Receive 1 byte
|
||||||
|
s->CR1 &= ~I2C_CR1_ACK; // Disable ack
|
||||||
|
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||||
|
// Next step will occur after a RXNE interrupt, so enable it
|
||||||
|
s->CR2 |= I2C_CR2_ITBUFEN;
|
||||||
|
transactionState = TS_R_STOP;
|
||||||
|
} else if (bytesToReceive == 2) {
|
||||||
|
// Receive 2 bytes
|
||||||
|
s->CR1 &= ~I2C_CR1_ACK; // Disable ACK for final byte
|
||||||
|
s->CR1 |= I2C_CR1_POS; // set POS flag to delay effect of ACK flag
|
||||||
|
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
|
||||||
|
s->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||||
|
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||||
|
transactionState = TS_R_STOP;
|
||||||
|
} else {
|
||||||
|
// >2 bytes, just wait for bytes to come in and ack them for the time being
|
||||||
|
// (ack flag has already been set).
|
||||||
|
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
|
||||||
|
s->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||||
|
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||||
|
transactionState = TS_R_DATA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_R_DATA:
|
||||||
|
// Event EV7/EV7_1
|
||||||
|
if (temp_sr1 & I2C_SR1_BTF) {
|
||||||
|
// Byte received in receiver - read next byte
|
||||||
|
if (bytesToReceive == 3) {
|
||||||
|
// Getting close to the last byte, so a specific sequence is recommended.
|
||||||
|
s->CR1 &= ~I2C_CR1_ACK; // Reset ack for next byte received.
|
||||||
|
transactionState = TS_R_STOP;
|
||||||
|
}
|
||||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||||
bytesToReceive--;
|
bytesToReceive--;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TS_R_STOP:
|
||||||
|
if (temp_sr1 & I2C_SR1_BTF) {
|
||||||
|
// Event EV7 (last one)
|
||||||
|
// When we've got here, the receiver has got the last two bytes
|
||||||
|
// (or one byte, if only one byte is being received),
|
||||||
|
// and NAK has already been sent, so we need to read from the receiver.
|
||||||
|
if (bytesToReceive) {
|
||||||
|
if (bytesToReceive > 1)
|
||||||
|
I2C_sendStop();
|
||||||
|
while(bytesToReceive) {
|
||||||
|
receiveBuffer[rxCount++] = s->DR; // Store received byte(s)
|
||||||
|
bytesToReceive--;
|
||||||
}
|
}
|
||||||
|
// Finish.
|
||||||
|
transactionState = TS_IDLE;
|
||||||
|
completionStatus = I2C_STATUS_OK;
|
||||||
|
state = I2C_STATE_COMPLETED;
|
||||||
|
}
|
||||||
|
} else if (temp_sr1 & I2C_SR1_RXNE) {
|
||||||
|
if (bytesToReceive == 1) {
|
||||||
|
// One byte on a single-byte transfer. Ack has already been set.
|
||||||
|
I2C_sendStop();
|
||||||
|
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||||
|
bytesToReceive--;
|
||||||
|
// Finish.
|
||||||
|
transactionState = TS_IDLE;
|
||||||
|
completionStatus = I2C_STATUS_OK;
|
||||||
|
state = I2C_STATE_COMPLETED;
|
||||||
|
} else
|
||||||
|
s->SR1 &= I2C_SR1_RXNE; // Acknowledge interrupt
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// If we've received an interrupt at any other time, we're not interested so clear it
|
||||||
|
// to prevent it recurring ad infinitum.
|
||||||
|
s->SR1 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* I2CMANAGER_STM32_H */
|
#endif /* I2CMANAGER_STM32_H */
|
||||||
|
|
53
IODevice.cpp
53
IODevice.cpp
|
@ -27,13 +27,19 @@
|
||||||
#include "IO_MCP23017.h"
|
#include "IO_MCP23017.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
|
|
||||||
|
#if !defined(IO_NO_HAL)
|
||||||
|
#ifdef FAST_CLOCK_I2C
|
||||||
|
#include "IO_EXFastClock.h" // FastClock driver
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
|
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
|
||||||
#define USE_FAST_IO
|
#define USE_FAST_IO
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Link to halSetup function. If not defined, the function reference will be NULL.
|
// Link to halSetup function. If not defined, the function reference will be NULL.
|
||||||
extern __attribute__((weak)) void halSetup();
|
extern __attribute__((weak)) void halSetup();
|
||||||
extern __attribute__((weak)) void exrailHalSetup();
|
extern __attribute__((weak)) bool exrailHalSetup();
|
||||||
|
|
||||||
//==================================================================================================================
|
//==================================================================================================================
|
||||||
// Static methods
|
// Static methods
|
||||||
|
@ -60,33 +66,35 @@ void IODevice::begin() {
|
||||||
halSetup();
|
halSetup();
|
||||||
|
|
||||||
// include any HAL devices defined in exrail.
|
// include any HAL devices defined in exrail.
|
||||||
|
bool ignoreDefaults=false;
|
||||||
if (exrailHalSetup)
|
if (exrailHalSetup)
|
||||||
exrailHalSetup();
|
ignoreDefaults=exrailHalSetup();
|
||||||
|
if (ignoreDefaults) return;
|
||||||
|
|
||||||
|
#ifdef FAST_CLOCK_I2C
|
||||||
|
// DIAG(F("EXFastClock::create"));
|
||||||
|
EXFastClock::create(FAST_CLOCK_I2C);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
|
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
|
||||||
// Allocates 32 pins 100-131
|
// Allocates 32 pins 100-131
|
||||||
if (checkNoOverlap(100, 16, 0x40)) {
|
const bool silent=true; // no message if these conflict
|
||||||
|
if (checkNoOverlap(100, 16, 0x40, silent)) {
|
||||||
PCA9685::create(100, 16, 0x40);
|
PCA9685::create(100, 16, 0x40);
|
||||||
} else {
|
|
||||||
DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device"));
|
|
||||||
}
|
}
|
||||||
if (checkNoOverlap(116, 16, 0x41)) {
|
|
||||||
|
if (checkNoOverlap(116, 16, 0x41, silent)) {
|
||||||
PCA9685::create(116, 16, 0x41);
|
PCA9685::create(116, 16, 0x41);
|
||||||
} else {
|
|
||||||
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
|
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
|
||||||
// Allocates 32 pins 164-195
|
// Allocates 32 pins 164-195
|
||||||
if (checkNoOverlap(164, 16, 0x20)) {
|
if (checkNoOverlap(164, 16, 0x20, silent)) {
|
||||||
MCP23017::create(164, 16, 0x20);
|
MCP23017::create(164, 16, 0x20);
|
||||||
} else {
|
|
||||||
DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device"));
|
|
||||||
}
|
}
|
||||||
if (checkNoOverlap(180, 16, 0x21)) {
|
|
||||||
|
if (checkNoOverlap(180, 16, 0x21, silent)) {
|
||||||
MCP23017::create(180, 16, 0x21);
|
MCP23017::create(180, 16, 0x21);
|
||||||
} else {
|
|
||||||
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +184,13 @@ bool IODevice::exists(VPIN vpin) {
|
||||||
return findDevice(vpin) != NULL;
|
return findDevice(vpin) != NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return the status of the device att vpin.
|
||||||
|
uint8_t IODevice::getStatus(VPIN vpin) {
|
||||||
|
IODevice *dev = findDevice(vpin);
|
||||||
|
if (!dev) return false;
|
||||||
|
return dev->_deviceState;
|
||||||
|
}
|
||||||
|
|
||||||
// check whether the pin supports notification. If so, then regular _read calls are not required.
|
// check whether the pin supports notification. If so, then regular _read calls are not required.
|
||||||
bool IODevice::hasCallback(VPIN vpin) {
|
bool IODevice::hasCallback(VPIN vpin) {
|
||||||
IODevice *dev = findDevice(vpin);
|
IODevice *dev = findDevice(vpin);
|
||||||
|
@ -332,7 +347,10 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
|
||||||
// returns true if pins DONT overlap with existing device
|
// returns true if pins DONT overlap with existing device
|
||||||
// TODO: Move the I2C address reservation and checks into the I2CManager code.
|
// TODO: Move the I2C address reservation and checks into the I2CManager code.
|
||||||
// That will enable non-HAL devices to reserve I2C addresses too.
|
// That will enable non-HAL devices to reserve I2C addresses too.
|
||||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
|
// Silent is used by the default setup so that there is no message if the default
|
||||||
|
// device has already been handled by the user setup.
|
||||||
|
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins,
|
||||||
|
I2CAddress i2cAddress, bool silent) {
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
|
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
|
||||||
#endif
|
#endif
|
||||||
|
@ -345,14 +363,14 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres
|
||||||
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
||||||
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
||||||
if (!noOverlap) {
|
if (!noOverlap) {
|
||||||
DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
|
if (!silent) DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
|
||||||
firstPin, lastPin);
|
firstPin, lastPin);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check for overlapping I2C address
|
// Check for overlapping I2C address
|
||||||
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
|
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
|
||||||
DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
|
if (!silent) DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -582,4 +600,3 @@ bool ArduinoPins::fastReadDigital(uint8_t pin) {
|
||||||
#endif
|
#endif
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
19
IODevice.h
19
IODevice.h
|
@ -27,12 +27,6 @@
|
||||||
// Define symbol DIAG_LOOPTIMES to enable CS loop execution time to be reported
|
// Define symbol DIAG_LOOPTIMES to enable CS loop execution time to be reported
|
||||||
//#define DIAG_LOOPTIMES
|
//#define DIAG_LOOPTIMES
|
||||||
|
|
||||||
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
|
|
||||||
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
|
|
||||||
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
|
|
||||||
#define IO_NO_HAL
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Define symbol IO_SWITCH_OFF_SERVO to set the PCA9685 output to 0 when an
|
// Define symbol IO_SWITCH_OFF_SERVO to set the PCA9685 output to 0 when an
|
||||||
// animation has completed. This switches off the servo motor, preventing
|
// animation has completed. This switches off the servo motor, preventing
|
||||||
// the continuous buzz sometimes found on servos, and reducing the
|
// the continuous buzz sometimes found on servos, and reducing the
|
||||||
|
@ -160,6 +154,9 @@ public:
|
||||||
// exists checks whether there is a device owning the specified vpin
|
// exists checks whether there is a device owning the specified vpin
|
||||||
static bool exists(VPIN vpin);
|
static bool exists(VPIN vpin);
|
||||||
|
|
||||||
|
// getStatus returns the state of the device at the specified vpin
|
||||||
|
static uint8_t getStatus(VPIN vpin);
|
||||||
|
|
||||||
// Enable shared interrupt on specified pin for GPIO extender modules. The extender module
|
// Enable shared interrupt on specified pin for GPIO extender modules. The extender module
|
||||||
// should pull down this pin when requesting a scan. The pin may be shared by multiple modules.
|
// should pull down this pin when requesting a scan. The pin may be shared by multiple modules.
|
||||||
// Without the shared interrupt, input states are scanned periodically to detect changes on
|
// Without the shared interrupt, input states are scanned periodically to detect changes on
|
||||||
|
@ -169,7 +166,8 @@ public:
|
||||||
void setGPIOInterruptPin(int16_t pinNumber);
|
void setGPIOInterruptPin(int16_t pinNumber);
|
||||||
|
|
||||||
// Method to check if pins will overlap before creating new device.
|
// Method to check if pins will overlap before creating new device.
|
||||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0);
|
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1,
|
||||||
|
I2CAddress i2cAddress=0, bool silent=false);
|
||||||
|
|
||||||
// Method used by IODevice filters to locate slave pins that may be overlayed by their own
|
// Method used by IODevice filters to locate slave pins that may be overlayed by their own
|
||||||
// pin range.
|
// pin range.
|
||||||
|
@ -383,6 +381,7 @@ private:
|
||||||
uint8_t *_pinInUse;
|
uint8_t *_pinInUse;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
/*
|
/*
|
||||||
* IODevice subclass for EX-Turntable.
|
* IODevice subclass for EX-Turntable.
|
||||||
|
@ -411,10 +410,14 @@ private:
|
||||||
void _begin() override;
|
void _begin() override;
|
||||||
void _loop(unsigned long currentMicros) override;
|
void _loop(unsigned long currentMicros) override;
|
||||||
int _read(VPIN vpin) override;
|
int _read(VPIN vpin) override;
|
||||||
|
void _broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity);
|
||||||
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
|
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
|
||||||
void _display() override;
|
void _display() override;
|
||||||
uint8_t _stepperStatus;
|
uint8_t _stepperStatus;
|
||||||
|
uint8_t _previousStatus;
|
||||||
|
uint8_t _currentActivity;
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -540,8 +543,10 @@ protected:
|
||||||
#include "IO_MCP23017.h"
|
#include "IO_MCP23017.h"
|
||||||
#include "IO_PCF8574.h"
|
#include "IO_PCF8574.h"
|
||||||
#include "IO_PCF8575.h"
|
#include "IO_PCF8575.h"
|
||||||
|
#include "IO_PCA9555.h"
|
||||||
#include "IO_duinoNodes.h"
|
#include "IO_duinoNodes.h"
|
||||||
#include "IO_EXIOExpander.h"
|
#include "IO_EXIOExpander.h"
|
||||||
|
#include "IO_trainbrains.h"
|
||||||
|
|
||||||
|
|
||||||
#endif // iodevice_h
|
#endif // iodevice_h
|
||||||
|
|
|
@ -51,17 +51,18 @@ static void create(I2CAddress i2cAddress) {
|
||||||
// Start by assuming we will find the clock
|
// Start by assuming we will find the clock
|
||||||
// Check if specified I2C address is responding (blocking operation)
|
// Check if specified I2C address is responding (blocking operation)
|
||||||
// Returns I2C_STATUS_OK (0) if OK, or error code.
|
// Returns I2C_STATUS_OK (0) if OK, or error code.
|
||||||
|
I2CManager.begin();
|
||||||
uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress);
|
uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress);
|
||||||
DIAG(F("Clock check result - %d"), _checkforclock);
|
DIAG(F("Clock check result - %d"), _checkforclock);
|
||||||
// XXXX change thistosave2 bytes
|
// XXXX change thistosave2 bytes
|
||||||
if (_checkforclock == 0) {
|
if (_checkforclock == 0) {
|
||||||
FAST_CLOCK_EXISTS = true;
|
FAST_CLOCK_EXISTS = true;
|
||||||
//DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString());
|
DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString());
|
||||||
new EXFastClock(i2cAddress);
|
new EXFastClock(i2cAddress);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
FAST_CLOCK_EXISTS = false;
|
FAST_CLOCK_EXISTS = false;
|
||||||
//DIAG(F("No Fast Clock found"));
|
DIAG(F("No Fast Clock found"));
|
||||||
LCD(6,F("CLOCK NOT FOUND"));
|
LCD(6,F("CLOCK NOT FOUND"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +96,8 @@ void _loop(unsigned long currentMicros) override{
|
||||||
if (FAST_CLOCK_EXISTS==true) {
|
if (FAST_CLOCK_EXISTS==true) {
|
||||||
uint8_t readBuffer[3];
|
uint8_t readBuffer[3];
|
||||||
byte a,b;
|
byte a,b;
|
||||||
#ifdef EXRAIL_ACTIVE
|
// I would like to use the FastClock without EXRAIL
|
||||||
|
// #ifdef EXRAIL_ACTIVE
|
||||||
I2CManager.read(_I2CAddress, readBuffer, 3);
|
I2CManager.read(_I2CAddress, readBuffer, 3);
|
||||||
// XXXX change this to save a few bytes
|
// XXXX change this to save a few bytes
|
||||||
a = readBuffer[0];
|
a = readBuffer[0];
|
||||||
|
@ -110,7 +112,7 @@ void _loop(unsigned long currentMicros) override{
|
||||||
// Clock interval is 60/ clockspeed i.e 60/b seconds
|
// Clock interval is 60/ clockspeed i.e 60/b seconds
|
||||||
delayUntil(currentMicros + ((60/b) * 1000000));
|
delayUntil(currentMicros + ((60/b) * 1000000));
|
||||||
|
|
||||||
#endif
|
// #endif
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* © 2022, Peter Cole. All rights reserved.
|
* © 2022, Peter Cole. All rights reserved.
|
||||||
* © 2024, Harald Barth. All rights reserved.
|
* © 2024, Harald Barth. All rights reserved.
|
||||||
|
* © 2024, Harald Barth. All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of EX-CommandStation
|
* This file is part of EX-CommandStation
|
||||||
*
|
*
|
||||||
|
@ -23,13 +24,10 @@
|
||||||
* This device driver will configure the device on startup, along with
|
* This device driver will configure the device on startup, along with
|
||||||
* interacting with the device for all input/output duties.
|
* interacting with the device for all input/output duties.
|
||||||
*
|
*
|
||||||
* To create EX-IOExpander devices, these are defined in myHal.cpp:
|
* To create EX-IOExpander devices, these are defined in myAutomation.h:
|
||||||
* (Note the device driver is included by default)
|
* (Note the device driver is included by default)
|
||||||
*
|
*
|
||||||
* void halSetup() {
|
* HAL(EXIOExpander,800,18,0x65)
|
||||||
* // EXIOExpander::create(vpin, num_vpins, i2c_address);
|
|
||||||
* EXIOExpander::create(800, 18, 0x65);
|
|
||||||
* }
|
|
||||||
*
|
*
|
||||||
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific
|
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific
|
||||||
* device in use. There is no way for the device driver to sanity check pins are used for the
|
* device in use. There is no way for the device driver to sanity check pins are used for the
|
||||||
|
|
|
@ -20,20 +20,21 @@
|
||||||
/*
|
/*
|
||||||
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
|
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
|
||||||
*
|
*
|
||||||
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
|
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/EX-Turntable) and contains the stepper motor logic.
|
||||||
*
|
*
|
||||||
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
|
* This device driver sends a step position to EX-Turntable to indicate the step position to move to using either of these commands:
|
||||||
* <D TT vpin steps activity> in the serial console
|
* <D TT vpin steps activity> in the serial console
|
||||||
* MOVETT(vpin, steps, activity) in EX-RAIL
|
* MOVETT(vpin, steps, activity) in EX-RAIL
|
||||||
* Refer to the documentation for further information including the valid activities.
|
* Refer to the documentation for further information including the valid activities.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef IO_EXTurntable_h
|
|
||||||
#define IO_EXTurntable_h
|
|
||||||
|
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "I2CManager.h"
|
#include "I2CManager.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include "Turntables.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
|
||||||
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||||
new EXTurntable(firstVpin, nPins, I2CAddress);
|
new EXTurntable(firstVpin, nPins, I2CAddress);
|
||||||
|
@ -44,6 +45,8 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||||
_firstVpin = firstVpin;
|
_firstVpin = firstVpin;
|
||||||
_nPins = nPins;
|
_nPins = nPins;
|
||||||
_I2CAddress = I2CAddress;
|
_I2CAddress = I2CAddress;
|
||||||
|
_stepperStatus = 0;
|
||||||
|
_previousStatus = 0;
|
||||||
addDevice(this);
|
addDevice(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +54,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||||
void EXTurntable::_begin() {
|
void EXTurntable::_begin() {
|
||||||
I2CManager.begin();
|
I2CManager.begin();
|
||||||
if (I2CManager.exists(_I2CAddress)) {
|
if (I2CManager.exists(_I2CAddress)) {
|
||||||
|
DIAG(F("EX-Turntable device found, I2C:%s"), _I2CAddress.toString());
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
_display();
|
_display();
|
||||||
#endif
|
#endif
|
||||||
|
@ -67,15 +71,20 @@ void EXTurntable::_loop(unsigned long currentMicros) {
|
||||||
uint8_t readBuffer[1];
|
uint8_t readBuffer[1];
|
||||||
I2CManager.read(_I2CAddress, readBuffer, 1);
|
I2CManager.read(_I2CAddress, readBuffer, 1);
|
||||||
_stepperStatus = readBuffer[0];
|
_stepperStatus = readBuffer[0];
|
||||||
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
|
if (_stepperStatus != _previousStatus && _stepperStatus == 0) { // Broadcast when a rotation finishes
|
||||||
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
|
if ( _currentActivity < 4) {
|
||||||
|
_broadcastStatus(_firstVpin, _stepperStatus, _currentActivity);
|
||||||
|
}
|
||||||
|
_previousStatus = _stepperStatus;
|
||||||
|
}
|
||||||
|
delayUntil(currentMicros + 100000); // Wait 100ms before checking again
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read returns status as obtained in our loop.
|
// Read returns status as obtained in our loop.
|
||||||
// Return false if our status value is invalid.
|
// Return false if our status value is invalid.
|
||||||
int EXTurntable::_read(VPIN vpin) {
|
int EXTurntable::_read(VPIN vpin) {
|
||||||
|
(void)vpin; // surpress warning
|
||||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||||
// DIAG(F("_read status: %d"), _stepperStatus);
|
|
||||||
if (_stepperStatus > 1) {
|
if (_stepperStatus > 1) {
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,6 +92,17 @@ int EXTurntable::_read(VPIN vpin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a status change has occurred for a turntable object, broadcast it
|
||||||
|
void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity) {
|
||||||
|
Turntable *tto = Turntable::getByVpin(vpin);
|
||||||
|
if (tto) {
|
||||||
|
if (activity < 4) {
|
||||||
|
tto->setMoving(status);
|
||||||
|
CommandDistributor::broadcastTurntable(tto->getId(), tto->getPosition(), status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// writeAnalogue to send the steps and activity to Turntable-EX.
|
// writeAnalogue to send the steps and activity to Turntable-EX.
|
||||||
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
|
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
|
||||||
// value contains the steps, bit shifted to MSB + LSB.
|
// value contains the steps, bit shifted to MSB + LSB.
|
||||||
|
@ -100,6 +120,7 @@ int EXTurntable::_read(VPIN vpin) {
|
||||||
// Acc_Off = 9 // Turn accessory pin off
|
// Acc_Off = 9 // Turn accessory pin off
|
||||||
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
|
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
|
||||||
if (_deviceState == DEVSTATE_FAILED) return;
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
|
if (value < 0) return;
|
||||||
uint8_t stepsMSB = value >> 8;
|
uint8_t stepsMSB = value >> 8;
|
||||||
uint8_t stepsLSB = value & 0xFF;
|
uint8_t stepsLSB = value & 0xFF;
|
||||||
#ifdef DIAG_IO
|
#ifdef DIAG_IO
|
||||||
|
@ -107,8 +128,13 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||||
vpin, value, activity, duration);
|
vpin, value, activity, duration);
|
||||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||||
|
#else
|
||||||
|
(void)duration;
|
||||||
#endif
|
#endif
|
||||||
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||||
|
_previousStatus = _stepperStatus;
|
||||||
|
_currentActivity = activity;
|
||||||
|
_broadcastStatus(vpin, _stepperStatus, activity); // Broadcast when the rotation starts
|
||||||
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
|
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* © 2023, Neil McKechnie. All rights reserved.
|
* © 2024, Paul Antoine
|
||||||
|
* © 2023, Neil McKechnie
|
||||||
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC++EX API
|
* This file is part of DCC-EX API
|
||||||
*
|
*
|
||||||
* This is free software: you can redistribute it and/or modify
|
* This is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
@ -112,13 +114,14 @@ protected:
|
||||||
// Fill buffer with spaces
|
// Fill buffer with spaces
|
||||||
memset(_buffer, ' ', _numCols*_numRows);
|
memset(_buffer, ' ', _numCols*_numRows);
|
||||||
|
|
||||||
_displayDriver->clearNative();
|
|
||||||
|
|
||||||
// Add device to list of HAL devices (not necessary but allows
|
// Add device to list of HAL devices (not necessary but allows
|
||||||
// status to be displayed using <D HAL SHOW> and device to be
|
// status to be displayed using <D HAL SHOW> and device to be
|
||||||
// reinitialised using <D HAL RESET>).
|
// reinitialised using <D HAL RESET>).
|
||||||
IODevice::addDevice(this);
|
IODevice::addDevice(this);
|
||||||
|
|
||||||
|
// Moved after addDevice() to ensure I2CManager.begin() has been called fisrt
|
||||||
|
_displayDriver->clearNative();
|
||||||
|
|
||||||
// Also add this display to list of display handlers
|
// Also add this display to list of display handlers
|
||||||
DisplayInterface::addDisplay(displayNo);
|
DisplayInterface::addDisplay(displayNo);
|
||||||
|
|
||||||
|
|
805
IO_I2CDFPlayer.h
Normal file
805
IO_I2CDFPlayer.h
Normal file
|
@ -0,0 +1,805 @@
|
||||||
|
/*
|
||||||
|
* © 2023, 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
|
||||||
|
* amplifier, so it only needs a power supply and a speaker.
|
||||||
|
* This driver is a modified version of the IO_DFPlayer.h file
|
||||||
|
* *********************************************************************************************
|
||||||
|
*
|
||||||
|
* Dec 2023, Added NXP SC16IS752 I2C Dual UART to enable the DFPlayer connection over the I2C bus
|
||||||
|
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
||||||
|
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
||||||
|
* needed as the RX Fifo holds the reply
|
||||||
|
*
|
||||||
|
* Jan 2024, Issue with using both UARTs simultaniously, the secod uart seems to work but the first transmit
|
||||||
|
* corrupt data. This need more analysis and experimenatation.
|
||||||
|
* Will push this driver to the dev branch with the uart fixed to 0
|
||||||
|
* Both SC16IS750 (single uart) and SC16IS752 (dual uart, but only uart 0 is enable)
|
||||||
|
*
|
||||||
|
* myHall.cpp configuration syntax:
|
||||||
|
*
|
||||||
|
* I2CDFPlayer::create(1st vPin, vPins, I2C address, xtal);
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||||
|
* vPins : Total number of virtual pins allocated (2 vPins are supported, one for each UART)
|
||||||
|
* 1st vPin for UART 0, 2nd for UART 1
|
||||||
|
* I2C Address : I2C address of the serial controller, in 0x format
|
||||||
|
* xtal : 0 for 1,8432Mhz, 1 for 14,7456Mhz
|
||||||
|
*
|
||||||
|
* The vPin is also a pin that can be read, it indicate if the DFPlayer has finished playing a track
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef IO_I2CDFPlayer_h
|
||||||
|
#define IO_I2CDFPlayer_h
|
||||||
|
|
||||||
|
#include "IODevice.h"
|
||||||
|
#include "I2CManager.h"
|
||||||
|
#include "DIAG.h"
|
||||||
|
|
||||||
|
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
||||||
|
//#define DIAG_I2CDFplayer
|
||||||
|
//#define DIAG_I2CDFplayer_data
|
||||||
|
//#define DIAG_I2CDFplayer_reg
|
||||||
|
//#define DIAG_I2CDFplayer_playing
|
||||||
|
|
||||||
|
class I2CDFPlayer : public IODevice {
|
||||||
|
private:
|
||||||
|
const uint8_t MAXVOLUME=30;
|
||||||
|
uint8_t RETRYCOUNT = 0x03;
|
||||||
|
bool _playing = false;
|
||||||
|
uint8_t _inputIndex = 0;
|
||||||
|
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||||
|
unsigned long _timeoutTime;
|
||||||
|
uint8_t _recvCMD; // Last received command code byte
|
||||||
|
bool _awaitingResponse = false;
|
||||||
|
uint8_t _retryCounter = RETRYCOUNT; // Max retries before timing out
|
||||||
|
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||||
|
uint8_t _currentVolume = MAXVOLUME;
|
||||||
|
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||||
|
bool _repeat = false; // audio file is repeat playing
|
||||||
|
uint8_t _previousCmd = true;
|
||||||
|
// SC16IS752 defines
|
||||||
|
I2CAddress _I2CAddress;
|
||||||
|
I2CRB _rb;
|
||||||
|
uint8_t _UART_CH=0x00; // Fix uart ch to 0 for now
|
||||||
|
// Communication parameters for the DFPlayer are fixed at 8 bit, No parity, 1 stopbit
|
||||||
|
uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
||||||
|
uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
||||||
|
uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
||||||
|
uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
||||||
|
uint32_t BAUD_RATE = 9600;
|
||||||
|
uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
||||||
|
uint8_t TEMP_REG_VAL = 0x00;
|
||||||
|
uint8_t FIFO_RX_LEVEL = 0x00;
|
||||||
|
uint8_t RX_BUFFER = 0x00; // nr of bytes copied into _inbuffer
|
||||||
|
uint8_t FIFO_TX_LEVEL = 0x00;
|
||||||
|
bool _playCmd = false;
|
||||||
|
bool _volCmd = false;
|
||||||
|
bool _folderCmd = false;
|
||||||
|
uint8_t _requestedFolder = 0x01; // default to folder 01
|
||||||
|
uint8_t _currentFolder = 0x01; // default to folder 01
|
||||||
|
bool _repeatCmd = false;
|
||||||
|
bool _stopplayCmd = false;
|
||||||
|
bool _resetCmd = false;
|
||||||
|
bool _eqCmd = false;
|
||||||
|
uint8_t _requestedEQValue = DF_NORMAL;
|
||||||
|
uint8_t _currentEQvalue = DF_NORMAL; // start equalizer value
|
||||||
|
bool _daconCmd = false;
|
||||||
|
uint8_t _audioMixer = 0x01; // Default to output amplifier 1
|
||||||
|
bool _setamCmd = false; // Set the Audio mixer channel
|
||||||
|
uint8_t _outbuffer [11]; // DFPlayer command is 10 bytes + 1 byte register address & UART channel
|
||||||
|
uint8_t _inbuffer[10]; // expected DFPlayer return 10 bytes
|
||||||
|
|
||||||
|
unsigned long _sc16is752_xtal_freq;
|
||||||
|
unsigned long SC16IS752_XTAL_FREQ_LOW = 1843200; // To support cheap eBay/AliExpress SC16IS752 boards
|
||||||
|
unsigned long SC16IS752_XTAL_FREQ_HIGH = 14745600; // Support for higher baud rates, standard for modular EX-IO system
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructor
|
||||||
|
I2CDFPlayer(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t xtal){
|
||||||
|
_firstVpin = firstVpin;
|
||||||
|
_nPins = nPins;
|
||||||
|
_I2CAddress = i2cAddress;
|
||||||
|
if (xtal == 0){
|
||||||
|
_sc16is752_xtal_freq = SC16IS752_XTAL_FREQ_LOW;
|
||||||
|
} else { // should be 1
|
||||||
|
_sc16is752_xtal_freq = SC16IS752_XTAL_FREQ_HIGH;
|
||||||
|
}
|
||||||
|
addDevice(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t xtal) {
|
||||||
|
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new I2CDFPlayer(firstVpin, nPins, i2cAddress, xtal);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _begin() override {
|
||||||
|
// check if SC16IS752 exist first, initialize and then resume DFPlayer init via SC16IS752
|
||||||
|
I2CManager.begin();
|
||||||
|
I2CManager.setClock(1000000);
|
||||||
|
if (I2CManager.exists(_I2CAddress)){
|
||||||
|
DIAG(F("SC16IS752 I2C:%s UART detected"), _I2CAddress.toString());
|
||||||
|
Init_SC16IS752(); // Initialize UART
|
||||||
|
if (_deviceState == DEVSTATE_FAILED){
|
||||||
|
DIAG(F("SC16IS752 I2C:%s UART initialization failed"), _I2CAddress.toString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DIAG(F("SC16IS752 I2C:%s UART not detected"), _I2CAddress.toString());
|
||||||
|
}
|
||||||
|
#if defined(DIAG_IO)
|
||||||
|
_display();
|
||||||
|
#endif
|
||||||
|
// Now init DFPlayer
|
||||||
|
// Send a query to the device to see if it responds
|
||||||
|
_deviceState = DEVSTATE_INITIALISING;
|
||||||
|
sendPacket(0x42,0,0);
|
||||||
|
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||||
|
_awaitingResponse = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void _loop(unsigned long currentMicros) override {
|
||||||
|
// Read responses from device
|
||||||
|
uint8_t status = _rb.status;
|
||||||
|
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
|
processIncoming(currentMicros);
|
||||||
|
// Check if a command sent to device has timed out. Allow 0.5 second for response
|
||||||
|
// added retry counter, sometimes we do not sent keep alive due to other commands sent to DFPlayer
|
||||||
|
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { // timeout triggered
|
||||||
|
if(_retryCounter == 0){ // retry counter out of luck, must take the device to failed state
|
||||||
|
DIAG(F("I2CDFPlayer:%s, DFPlayer not responding on UART channel: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||||
|
_deviceState = DEVSTATE_FAILED;
|
||||||
|
_awaitingResponse = false;
|
||||||
|
_playing = false;
|
||||||
|
_retryCounter = RETRYCOUNT;
|
||||||
|
} else { // timeout and retry protection and recovery of corrupt data frames from DFPlayer
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: %s, DFPlayer timout, retry counter: %d on UART channel: 0x%x"), _I2CAddress.toString(), _retryCounter, _UART_CH);
|
||||||
|
#endif
|
||||||
|
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds// reset timeout
|
||||||
|
_awaitingResponse = false; // trigger sending a keep alive 0x42 in processOutgoing()
|
||||||
|
_retryCounter --; // decrement retry counter
|
||||||
|
resetRX_fifo(); // reset the RX fifo as it has corrupt data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status = _rb.status;
|
||||||
|
if (status == I2C_STATUS_PENDING) return; // Busy, try next time
|
||||||
|
if (status == I2C_STATUS_OK) {
|
||||||
|
// Send any commands that need to go.
|
||||||
|
processOutgoing(currentMicros);
|
||||||
|
}
|
||||||
|
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Check for incoming data, and update busy flag and other state accordingly
|
||||||
|
|
||||||
|
void processIncoming(unsigned long currentMicros) {
|
||||||
|
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||||
|
RX_fifo_lvl();
|
||||||
|
if (FIFO_RX_LEVEL >= 10) {
|
||||||
|
#ifdef DIAG_I2CDFplayer
|
||||||
|
DIAG(F("I2CDFPlayer: %s Retrieving data from RX Fifo on UART_CH: 0x%x FIFO_RX_LEVEL: %d"),_I2CAddress.toString(), _UART_CH, FIFO_RX_LEVEL);
|
||||||
|
#endif
|
||||||
|
_outbuffer[0] = REG_RHR << 3 | _UART_CH << 1;
|
||||||
|
// Only copy 10 bytes from RX FIFO, there maybe additional partial return data after a track is finished playing in the RX FIFO
|
||||||
|
I2CManager.read(_I2CAddress, _inbuffer, 10, _outbuffer, 1); // inbuffer[] has the data now
|
||||||
|
//delayUntil(currentMicros + 10000); // Allow time to get the data
|
||||||
|
RX_BUFFER = 10; // We have copied 10 bytes from RX FIFO to _inbuffer
|
||||||
|
#ifdef DIAG_I2CDFplayer_data
|
||||||
|
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX FIFO Data"), _I2CAddress.toString(), _UART_CH);
|
||||||
|
for (int i = 0; i < sizeof _inbuffer; i++){
|
||||||
|
DIAG(F("SC16IS752: Data _inbuffer[0x%x]: 0x%x"), i, _inbuffer[i]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
FIFO_RX_LEVEL = 0; //set to 0, we'll read a fresh FIFO_RX_LEVEL next time
|
||||||
|
return; // No data or not enough data in rx fifo, check again next time around
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ok = false;
|
||||||
|
//DIAG(F("I2CDFPlayer: RX_BUFFER: %d"), RX_BUFFER);
|
||||||
|
while (RX_BUFFER != 0) {
|
||||||
|
int c = _inbuffer[_inputIndex]; // Start at 0, increment to FIFO_RX_LEVEL
|
||||||
|
switch (_inputIndex) {
|
||||||
|
case 0:
|
||||||
|
if (c == 0x7E) ok = true;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
if (c == 0xFF) ok = true;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
if (c== 0x06) ok = true;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
_recvCMD = c; // CMD byte
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
switch (_recvCMD) {
|
||||||
|
//DIAG(F("I2CDFPlayer: %s, _recvCMD: 0x%x _awaitingResponse: 0x0%x"),_I2CAddress.toString(), _recvCMD, _awaitingResponse);
|
||||||
|
case 0x42:
|
||||||
|
// Response to status query
|
||||||
|
_playing = (c != 0);
|
||||||
|
// Mark the device online and cancel timeout
|
||||||
|
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||||
|
_deviceState = DEVSTATE_NORMAL;
|
||||||
|
#ifdef DIAG_I2CDFplayer
|
||||||
|
DIAG(F("I2CDFPlayer: %s, UART_CH: 0x0%x, _deviceState: 0x0%x"),_I2CAddress.toString(), _UART_CH, _deviceState);
|
||||||
|
#endif
|
||||||
|
#ifdef DIAG_IO
|
||||||
|
_display();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
_awaitingResponse = false;
|
||||||
|
break;
|
||||||
|
case 0x3d:
|
||||||
|
// End of play
|
||||||
|
if (_playing) {
|
||||||
|
#ifdef DIAG_IO
|
||||||
|
DIAG(F("I2CDFPlayer: Finished"));
|
||||||
|
#endif
|
||||||
|
_playing = false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 0x40:
|
||||||
|
// Error codes; 1: Module Busy
|
||||||
|
DIAG(F("I2CDFPlayer: Error %d returned from device"), c);
|
||||||
|
_playing = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
break;
|
||||||
|
case 4: case 5: case 7: case 8:
|
||||||
|
ok = true; // Skip over these bytes in message.
|
||||||
|
break;
|
||||||
|
case 9:
|
||||||
|
if (c==0xef) {
|
||||||
|
// Message finished
|
||||||
|
_retryCounter = RETRYCOUNT; // reset the retry counter as we have received a valid packet
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ok){
|
||||||
|
_inputIndex++; // character as expected, so increment index
|
||||||
|
RX_BUFFER --; // Decrease FIFO_RX_LEVEL with each character read from _inbuffer[_inputIndex]
|
||||||
|
} else {
|
||||||
|
_inputIndex = 0; // otherwise reset.
|
||||||
|
RX_BUFFER = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RX_BUFFER = 0; //Set to 0, we'll read a new RX FIFO level again
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Send any commands that need to be sent
|
||||||
|
void processOutgoing(unsigned long currentMicros) {
|
||||||
|
// When two commands are sent in quick succession, the device will often fail to
|
||||||
|
// execute one. Testing has indicated that a delay of 100ms or more is required
|
||||||
|
// between successive commands to get reliable operation.
|
||||||
|
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
|
||||||
|
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||||
|
if ( _resetCmd == true){
|
||||||
|
sendPacket(0x0C,0,0);
|
||||||
|
_resetCmd = false;
|
||||||
|
} else if(_volCmd == true) { // do the volme before palying a track
|
||||||
|
if(_requestedVolumeLevel >= 0 && _requestedVolumeLevel <= 30){
|
||||||
|
_currentVolume = _requestedVolumeLevel; // If _requestedVolumeLevel is out of range, sent _currentV1olume
|
||||||
|
}
|
||||||
|
sendPacket(0x06, 0x00, _currentVolume);
|
||||||
|
_volCmd = false;
|
||||||
|
} else if (_playCmd == true) {
|
||||||
|
// Change song
|
||||||
|
if (_requestedSong != -1) {
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: _requestedVolumeLevel: %u, _requestedSong: %u, _currentFolder: %u _playCmd: 0x%x"), _requestedVolumeLevel, _requestedSong, _currentFolder, _playCmd);
|
||||||
|
#endif
|
||||||
|
sendPacket(0x0F, _currentFolder, _requestedSong); // audio file in folder
|
||||||
|
_requestedSong = -1;
|
||||||
|
_playCmd = false;
|
||||||
|
}
|
||||||
|
} //else if (_requestedSong == 0) {
|
||||||
|
else if (_stopplayCmd == true) {
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: Stop playing: _stopplayCmd: 0x%x"), _stopplayCmd);
|
||||||
|
#endif
|
||||||
|
sendPacket(0x16, 0x00, 0x00); // Stop playing
|
||||||
|
_requestedSong = -1;
|
||||||
|
_repeat = false; // reset repeat
|
||||||
|
_stopplayCmd = false;
|
||||||
|
} else if (_folderCmd == true) {
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: Folder: _folderCmd: 0x%x, _requestedFolder: %d"), _stopplayCmd, _requestedFolder);
|
||||||
|
#endif
|
||||||
|
if (_currentFolder != _requestedFolder){
|
||||||
|
_currentFolder = _requestedFolder;
|
||||||
|
}
|
||||||
|
_folderCmd = false;
|
||||||
|
} else if (_repeatCmd == true) {
|
||||||
|
if(_repeat == false) { // No repeat play currently
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: Repeat: _repeatCmd: 0x%x, _requestedSong: %d, _repeat: 0x0%x"), _repeatCmd, _requestedSong, _repeat);
|
||||||
|
#endif
|
||||||
|
sendPacket(0x08, 0x00, _requestedSong); // repeat playing audio file in root folder
|
||||||
|
_requestedSong = -1;
|
||||||
|
_repeat = true;
|
||||||
|
}
|
||||||
|
_repeatCmd= false;
|
||||||
|
} else if (_daconCmd == true) { // Always turn DAC on
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: DACON: _daconCmd: 0x%x"), _daconCmd);
|
||||||
|
#endif
|
||||||
|
sendPacket(0x1A,0,0x00);
|
||||||
|
_daconCmd = false;
|
||||||
|
} else if (_eqCmd == true){ // Set Equalizer, values 0x00 - 0x05
|
||||||
|
if (_currentEQvalue != _requestedEQValue){
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: EQ: _eqCmd: 0x%x, _currentEQvalue: 0x0%x, _requestedEQValue: 0x0%x"), _eqCmd, _currentEQvalue, _requestedEQValue);
|
||||||
|
#endif
|
||||||
|
_currentEQvalue = _requestedEQValue;
|
||||||
|
sendPacket(0x07,0x00,_currentEQvalue);
|
||||||
|
}
|
||||||
|
_eqCmd = false;
|
||||||
|
} else if (_setamCmd == true){ // Set Audio mixer channel
|
||||||
|
setGPIO(); // Set the audio mixer channel
|
||||||
|
/*
|
||||||
|
if (_audioMixer == 1){ // set to audio mixer 1
|
||||||
|
if (_UART_CH == 0){
|
||||||
|
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||||
|
} else { // must be UART 1
|
||||||
|
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||||
|
}
|
||||||
|
//_setamCmd = false;
|
||||||
|
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||||
|
} else { // set to audio mixer 2
|
||||||
|
if (_UART_CH == 0){
|
||||||
|
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 0 to Low
|
||||||
|
} else { // must be UART 1
|
||||||
|
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 1 to Low
|
||||||
|
}
|
||||||
|
//_setamCmd = false;
|
||||||
|
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||||
|
}*/
|
||||||
|
_setamCmd = false;
|
||||||
|
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
|
||||||
|
// Poll device every second that other commands aren't being sent,
|
||||||
|
// to check if it's still connected and responding.
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: Send keepalive") );
|
||||||
|
#endif
|
||||||
|
sendPacket(0x42,0,0);
|
||||||
|
if (!_awaitingResponse) {
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: Send keepalive, _awaitingResponse: 0x0%x"), _awaitingResponse );
|
||||||
|
#endif
|
||||||
|
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
|
||||||
|
_awaitingResponse = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Write to a vPin will do nothing
|
||||||
|
void _write(VPIN vpin, int value) override {
|
||||||
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
|
#ifdef DIAG_IO
|
||||||
|
DIAG(F("I2CDFPlayer: Writing to any vPin not supported"));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
|
||||||
|
// Volume may be specified as second parameter to writeAnalogue.
|
||||||
|
// If value is zero, the player stops playing.
|
||||||
|
// WriteAnalogue on second pin sets the output volume.
|
||||||
|
//
|
||||||
|
// WriteAnalogue to be done on first vpin
|
||||||
|
//
|
||||||
|
//void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||||
|
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t cmd=0) override {
|
||||||
|
if (_deviceState == DEVSTATE_FAILED) return;
|
||||||
|
#ifdef DIAG_IO
|
||||||
|
DIAG(F("I2CDFPlayer: VPIN:%u FileNo:%d Volume:%d Command:0x%x"), vpin, value, volume, cmd);
|
||||||
|
#endif
|
||||||
|
uint8_t pin = vpin - _firstVpin;
|
||||||
|
if (pin == 0) { // Enhanced DFPlayer commands, do nothing if not vPin 0
|
||||||
|
// Read command and value
|
||||||
|
switch (cmd){
|
||||||
|
//case NONE:
|
||||||
|
// DFPlayerCmd = cmd;
|
||||||
|
// break;
|
||||||
|
case DF_PLAY:
|
||||||
|
_playCmd = true;
|
||||||
|
_volCmd = true;
|
||||||
|
_requestedSong = value;
|
||||||
|
_requestedVolumeLevel = volume;
|
||||||
|
_playing = true;
|
||||||
|
break;
|
||||||
|
case DF_VOL:
|
||||||
|
_volCmd = true;
|
||||||
|
_requestedVolumeLevel = volume;
|
||||||
|
break;
|
||||||
|
case DF_FOLDER:
|
||||||
|
_folderCmd = true;
|
||||||
|
if (volume <= 0 || volume > 99){ // Range checking, valid values 1-99, else default to 1
|
||||||
|
_requestedFolder = 0x01; // if outside range, default to folder 01
|
||||||
|
} else {
|
||||||
|
_requestedFolder = volume;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DF_REPEATPLAY: // Need to check if _repeat == true, if so do nothing
|
||||||
|
if (_repeat == false) {
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: WriteAnalog Repeat: _repeat: 0x0%x, value: %d _repeatCmd: 0x%x"), _repeat, value, _repeatCmd);
|
||||||
|
#endif
|
||||||
|
_repeatCmd = true;
|
||||||
|
_requestedSong = value;
|
||||||
|
_requestedVolumeLevel = volume;
|
||||||
|
_playing = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DF_STOPPLAY:
|
||||||
|
_stopplayCmd = true;
|
||||||
|
break;
|
||||||
|
case DF_EQ:
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: WriteAnalog EQ: cmd: 0x%x, EQ value: 0x%x"), cmd, volume);
|
||||||
|
#endif
|
||||||
|
_eqCmd = true;
|
||||||
|
if (volume <= 0 || volume > 5) { // If out of range, default to NORMAL
|
||||||
|
_requestedEQValue = DF_NORMAL;
|
||||||
|
} else { // Valid EQ parameter range
|
||||||
|
_requestedEQValue = volume;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case DF_RESET:
|
||||||
|
_resetCmd = true;
|
||||||
|
break;
|
||||||
|
case DF_DACON: // Works, but without the DACOFF command limited value, except when not relying on DFPlayer default to turn the DAC on
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: WrtieAnalog DACON: cmd: 0x%x"), cmd);
|
||||||
|
#endif
|
||||||
|
_daconCmd = true;
|
||||||
|
break;
|
||||||
|
case DF_SETAM: // Set the audio mixer channel to 1 or 2
|
||||||
|
_setamCmd = true;
|
||||||
|
#ifdef DIAG_I2CDFplayer_playing
|
||||||
|
DIAG(F("I2CDFPlayer: WrtieAnalog SETAM: cmd: 0x%x"), cmd);
|
||||||
|
#endif
|
||||||
|
if (volume <= 0 || volume > 2) { // If out of range, default to 1
|
||||||
|
_audioMixer = 1;
|
||||||
|
} else { // Valid SETAM parameter in range
|
||||||
|
_audioMixer = volume; // _audioMixer valid values 1 or 2
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A read on any pin indicates if the player is still playing.
|
||||||
|
int _read(VPIN vpin) override {
|
||||||
|
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||||
|
uint8_t pin = vpin - _firstVpin;
|
||||||
|
if (pin == 0) { // Do nothing if not vPin 0
|
||||||
|
return _playing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _display() override {
|
||||||
|
DIAG(F("I2CDFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||||
|
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// DFPlayer command frame
|
||||||
|
// 7E FF 06 0F 00 01 01 xx xx EF
|
||||||
|
// 0 -> 7E is start code
|
||||||
|
// 1 -> FF is version
|
||||||
|
// 2 -> 06 is length
|
||||||
|
// 3 -> 0F is command
|
||||||
|
// 4 -> 00 is no receive
|
||||||
|
// 5~6 -> 01 01 is argument
|
||||||
|
// 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
|
||||||
|
// 9 -> EF is end code
|
||||||
|
|
||||||
|
void sendPacket(uint8_t command, uint8_t arg1 = 0, uint8_t arg2 = 0) {
|
||||||
|
FIFO_TX_LEVEL = 0; // Reset FIFO_TX_LEVEL
|
||||||
|
uint8_t out[] = {
|
||||||
|
0x7E,
|
||||||
|
0xFF,
|
||||||
|
06,
|
||||||
|
command,
|
||||||
|
00,
|
||||||
|
//static_cast<uint8_t>(arg >> 8),
|
||||||
|
//static_cast<uint8_t>(arg & 0x00ff),
|
||||||
|
arg1,
|
||||||
|
arg2,
|
||||||
|
00,
|
||||||
|
00,
|
||||||
|
0xEF };
|
||||||
|
|
||||||
|
setChecksum(out);
|
||||||
|
|
||||||
|
// Prepend the DFPlayer command with REG address and UART Channel in _outbuffer
|
||||||
|
_outbuffer[0] = REG_THR << 3 | _UART_CH << 1; //TX FIFO and UART Channel
|
||||||
|
for ( int i = 1; i < sizeof(out)+1 ; i++){
|
||||||
|
_outbuffer[i] = out[i-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef DIAG_I2CDFplayer_data
|
||||||
|
DIAG(F("SC16IS752: I2C: %s Sent packet function"), _I2CAddress.toString());
|
||||||
|
for (int i = 0; i < sizeof _outbuffer; i++){
|
||||||
|
DIAG(F("SC16IS752: Data _outbuffer[0x%x]: 0x%x"), i, _outbuffer[i]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
TX_fifo_lvl();
|
||||||
|
if(FIFO_TX_LEVEL > 0){ //FIFO is empty
|
||||||
|
I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer), &_rb);
|
||||||
|
//I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer));
|
||||||
|
#ifdef DIAG_I2CDFplayer
|
||||||
|
DIAG(F("SC16IS752: I2C: %s data transmit complete on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
DIAG(F("I2CDFPlayer at: %s, TX FIFO not empty on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||||
|
_deviceState = DEVSTATE_FAILED; // This should not happen
|
||||||
|
}
|
||||||
|
_commandSendTime = micros();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t calcChecksum(uint8_t* packet)
|
||||||
|
{
|
||||||
|
uint16_t sum = 0;
|
||||||
|
for (int i = 1; i < 7; i++)
|
||||||
|
{
|
||||||
|
sum += packet[i];
|
||||||
|
}
|
||||||
|
return -sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setChecksum(uint8_t* out)
|
||||||
|
{
|
||||||
|
uint16_t sum = calcChecksum(out);
|
||||||
|
out[7] = (sum >> 8);
|
||||||
|
out[8] = (sum & 0xff);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SC16IS752 functions
|
||||||
|
// Initialise SC16IS752 only for this channel
|
||||||
|
// First a software reset
|
||||||
|
// Enable FIFO and clear TX & RX FIFO
|
||||||
|
// Need to set the following registers
|
||||||
|
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
||||||
|
// IODIR set all bit to 1 indicating al are output
|
||||||
|
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
||||||
|
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
||||||
|
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
||||||
|
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
||||||
|
// DLH most significant part of divisor
|
||||||
|
// DLL least significant part of divisor
|
||||||
|
//
|
||||||
|
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
||||||
|
//
|
||||||
|
void Init_SC16IS752(){ // Return value is in _deviceState
|
||||||
|
#ifdef DIAG_I2CDFplayer
|
||||||
|
DIAG(F("SC16IS752: Initialize I2C: %s , UART Ch: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||||
|
#endif
|
||||||
|
//uint16_t _divisor = (SC16IS752_XTAL_FREQ / PRESCALER) / (BAUD_RATE * 16);
|
||||||
|
uint16_t _divisor = (_sc16is752_xtal_freq/PRESCALER)/(BAUD_RATE * 16); // Calculate _divisor for baudrate
|
||||||
|
TEMP_REG_VAL = 0x08; // UART Software reset
|
||||||
|
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||||
|
TEMP_REG_VAL = 0x00; // Set pins to GPIO mode
|
||||||
|
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||||
|
TEMP_REG_VAL = 0xFF; //Set all pins as output
|
||||||
|
UART_WriteRegister(REG_IODIR, TEMP_REG_VAL);
|
||||||
|
UART_ReadRegister(REG_IOSTATE); // Read current state as not to overwrite the other GPIO pins
|
||||||
|
TEMP_REG_VAL = _inbuffer[0];
|
||||||
|
setGPIO(); // Set the audio mixer channel
|
||||||
|
/*
|
||||||
|
if (_UART_CH == 0){ // Set Audio mixer channel
|
||||||
|
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||||
|
} else { // must be UART 1
|
||||||
|
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||||
|
}
|
||||||
|
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||||
|
*/
|
||||||
|
TEMP_REG_VAL = 0x07; // Reset FIFO, clear RX & TX FIFO
|
||||||
|
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||||
|
TEMP_REG_VAL = 0x00; // Set MCR to all 0, includes Clock divisor
|
||||||
|
UART_WriteRegister(REG_MCR, TEMP_REG_VAL);
|
||||||
|
TEMP_REG_VAL = 0x80 | WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE;
|
||||||
|
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch enabled
|
||||||
|
UART_WriteRegister(REG_DLL, (uint8_t)_divisor); // Write DLL
|
||||||
|
UART_WriteRegister(REG_DLH, (uint8_t)(_divisor >> 8)); // Write DLH
|
||||||
|
UART_ReadRegister(REG_LCR);
|
||||||
|
TEMP_REG_VAL = _inbuffer[0] & 0x7F; // Disable Divisor latch enabled bit
|
||||||
|
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch disabled
|
||||||
|
|
||||||
|
uint8_t status = _rb.status;
|
||||||
|
if (status != I2C_STATUS_OK) {
|
||||||
|
DIAG(F("SC16IS752: I2C: %s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||||
|
_deviceState = DEVSTATE_FAILED;
|
||||||
|
} else {
|
||||||
|
#ifdef DIAG_IO
|
||||||
|
DIAG(F("SC16IS752: I2C: %s, _deviceState: %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||||
|
#endif
|
||||||
|
_deviceState = DEVSTATE_NORMAL; // If I2C state is OK, then proceed to initialize DFPlayer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Read the Receive FIFO Level register (RXLVL), return a single unsigned integer
|
||||||
|
// of nr of characters in the RX FIFO, bit 6:0, 7 not used, set to zero
|
||||||
|
// value from 0 (0x00) to 64 (0x40) Only display if RX FIFO has data
|
||||||
|
// The RX fifo level is used to check if there are enough bytes to process a frame
|
||||||
|
void RX_fifo_lvl(){
|
||||||
|
UART_ReadRegister(REG_RXLV);
|
||||||
|
FIFO_RX_LEVEL = _inbuffer[0];
|
||||||
|
#ifdef DIAG_I2CDFplayer
|
||||||
|
if (FIFO_RX_LEVEL > 0){
|
||||||
|
//if (FIFO_RX_LEVEL > 0 && FIFO_RX_LEVEL < 10){
|
||||||
|
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_RX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, _inbuffer[0]);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// When a frame is transmitted from the DFPlayer to the serial port, and at the same time the CS is sending a 42 query
|
||||||
|
// the following two frames from the DFPlayer are corrupt. This result in the receive buffer being out of sync and the
|
||||||
|
// CS will complain and generate a timeout.
|
||||||
|
// The RX fifo has corrupt data and need to be flushed, this function does that
|
||||||
|
//
|
||||||
|
void resetRX_fifo(){
|
||||||
|
#ifdef DIAG_I2CDFplayer
|
||||||
|
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX fifo reset"), _I2CAddress.toString(), _UART_CH);
|
||||||
|
#endif
|
||||||
|
TEMP_REG_VAL = 0x03; // Reset RX fifo
|
||||||
|
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set or reset GPIO pin 0 and 1 depending on the UART ch
|
||||||
|
// This function may be modified in a future release to enable all 8 pins to be set or reset with EX-Rail
|
||||||
|
// for various auxilary functions
|
||||||
|
void setGPIO(){
|
||||||
|
UART_ReadRegister(REG_IOSTATE); // Get the current GPIO pins state from the IOSTATE register
|
||||||
|
TEMP_REG_VAL = _inbuffer[0];
|
||||||
|
if (_audioMixer == 1){ // set to audio mixer 1
|
||||||
|
if (_UART_CH == 0){
|
||||||
|
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||||
|
} else { // must be UART 1
|
||||||
|
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||||
|
}
|
||||||
|
} else { // set to audio mixer 2
|
||||||
|
if (_UART_CH == 0){
|
||||||
|
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 0 to Low
|
||||||
|
} else { // must be UART 1
|
||||||
|
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 1 to Low
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||||
|
_setamCmd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Read the Tranmit FIFO Level register (TXLVL), return a single unsigned integer
|
||||||
|
// of nr characters free in the TX FIFO, bit 6:0, 7 not used, set to zero
|
||||||
|
// value from 0 (0x00) to 64 (0x40)
|
||||||
|
//
|
||||||
|
void TX_fifo_lvl(){
|
||||||
|
UART_ReadRegister(REG_TXLV);
|
||||||
|
FIFO_TX_LEVEL = _inbuffer[0];
|
||||||
|
#ifdef DIAG_I2CDFplayer
|
||||||
|
// DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_TX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, FIFO_TX_LEVEL);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//void UART_WriteRegister(I2CAddress _I2CAddress, uint8_t _UART_CH, uint8_t UART_REG, uint8_t Val, I2CRB &_rb){
|
||||||
|
void UART_WriteRegister(uint8_t UART_REG, uint8_t Val){
|
||||||
|
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1;
|
||||||
|
_outbuffer[1] = Val;
|
||||||
|
#ifdef DIAG_I2CDFplayer_reg
|
||||||
|
DIAG(F("SC16IS752: Write register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _outbuffer[1]);
|
||||||
|
#endif
|
||||||
|
I2CManager.write(_I2CAddress, _outbuffer, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void UART_ReadRegister(uint8_t UART_REG){
|
||||||
|
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1; // _outbuffer[0] has now UART_REG and UART_CH
|
||||||
|
I2CManager.read(_I2CAddress, _inbuffer, 1, _outbuffer, 1);
|
||||||
|
// _inbuffer has the REG data
|
||||||
|
#ifdef DIAG_I2CDFplayer_reg
|
||||||
|
DIAG(F("SC16IS752: Read register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _inbuffer[0]);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// SC16IS752 General register set (from the datasheet)
|
||||||
|
enum : uint8_t{
|
||||||
|
REG_RHR = 0x00, // FIFO Read
|
||||||
|
REG_THR = 0x00, // FIFO Write
|
||||||
|
REG_IER = 0x01, // Interrupt Enable Register R/W
|
||||||
|
REG_FCR = 0x02, // FIFO Control Register Write
|
||||||
|
REG_IIR = 0x02, // Interrupt Identification Register Read
|
||||||
|
REG_LCR = 0x03, // Line Control Register R/W
|
||||||
|
REG_MCR = 0x04, // Modem Control Register R/W
|
||||||
|
REG_LSR = 0x05, // Line Status Register Read
|
||||||
|
REG_MSR = 0x06, // Modem Status Register Read
|
||||||
|
REG_SPR = 0x07, // Scratchpad Register R/W
|
||||||
|
REG_TCR = 0x06, // Transmission Control Register R/W
|
||||||
|
REG_TLR = 0x07, // Trigger Level Register R/W
|
||||||
|
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
||||||
|
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
||||||
|
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
||||||
|
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
||||||
|
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
||||||
|
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
||||||
|
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
||||||
|
};
|
||||||
|
|
||||||
|
// SC16IS752 Special register set
|
||||||
|
enum : uint8_t{
|
||||||
|
REG_DLL = 0x00, // Division registers R/W
|
||||||
|
REG_DLH = 0x01, // Division registers R/W
|
||||||
|
};
|
||||||
|
|
||||||
|
// SC16IS752 Enhanced regiter set
|
||||||
|
enum : uint8_t{
|
||||||
|
REG_EFR = 0X02, // Enhanced Features Register R/W
|
||||||
|
REG_XON1 = 0x04, // R/W
|
||||||
|
REG_XON2 = 0x05, // R/W
|
||||||
|
REG_XOFF1 = 0x06, // R/W
|
||||||
|
REG_XOFF2 = 0x07, // R/W
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// DFPlayer commands and values
|
||||||
|
// Declared in this scope
|
||||||
|
enum : uint8_t{
|
||||||
|
DF_PLAY = 0x0F,
|
||||||
|
DF_VOL = 0x06,
|
||||||
|
DF_FOLDER = 0x2B, // Not a DFPlayer command, used to set folder nr where audio file is
|
||||||
|
DF_REPEATPLAY = 0x08,
|
||||||
|
DF_STOPPLAY = 0x16,
|
||||||
|
DF_EQ = 0x07, // Set equaliser, require parameter NORMAL, POP, ROCK, JAZZ, CLASSIC or BASS
|
||||||
|
DF_RESET = 0x0C,
|
||||||
|
DF_DACON = 0x1A,
|
||||||
|
DF_SETAM = 0x2A, // Set audio mixer 1 or 2 for this DFPLayer
|
||||||
|
DF_NORMAL = 0x00, // Equalizer parameters
|
||||||
|
DF_POP = 0x01,
|
||||||
|
DF_ROCK = 0x02,
|
||||||
|
DF_JAZZ = 0x03,
|
||||||
|
DF_CLASSIC = 0x04,
|
||||||
|
DF_BASS = 0x05,
|
||||||
|
};
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IO_I2CDFPlayer_h
|
|
@ -30,20 +30,19 @@
|
||||||
|
|
||||||
class PCA9555 : public GPIOBase<uint16_t> {
|
class PCA9555 : public GPIOBase<uint16_t> {
|
||||||
public:
|
public:
|
||||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||||
new PCA9555(vpin, min(nPins,16), I2CAddress, interruptPin);
|
if (checkNoOverlap(vpin, nPins, i2cAddress)) new PCA9555(vpin,nPins, i2cAddress, interruptPin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
// Constructor
|
// Constructor
|
||||||
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
PCA9555(VPIN vpin, uint8_t nPins, I2CAddress I2CAddress, int interruptPin=-1)
|
||||||
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
|
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
|
||||||
{
|
{
|
||||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||||
outputBuffer, sizeof(outputBuffer));
|
outputBuffer, sizeof(outputBuffer));
|
||||||
outputBuffer[0] = REG_INPUT_P0;
|
outputBuffer[0] = REG_INPUT_P0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
void _writeGpioPort() override {
|
void _writeGpioPort() override {
|
||||||
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
|
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,9 +42,9 @@
|
||||||
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
|
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
|
||||||
*
|
*
|
||||||
* #include "IO_RotaryEncoder.h"
|
* #include "IO_RotaryEncoder.h"
|
||||||
* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software
|
* HAL(RotaryEncoder, 700, 1, 0x67) // Define single Vpin, no feedback or position sent to rotary encoder software
|
||||||
* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software
|
* HAL(RotaryEncoder, 700, 2, 0x67) // Define two Vpins, feedback only sent to rotary encoder software
|
||||||
* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software
|
* HAL(RotaryEncoder, 700, 3, 0x67) // Define three Vpins, can send feedback and position update to rotary encoder software
|
||||||
*
|
*
|
||||||
* Refer to the documentation for further information including the valid activities and examples.
|
* Refer to the documentation for further information including the valid activities and examples.
|
||||||
*/
|
*/
|
||||||
|
|
98
IO_trainbrains.h
Normal file
98
IO_trainbrains.h
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
/*
|
||||||
|
* © 2023, Chris Harlow. All rights reserved.
|
||||||
|
* © 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_trainbrains_h
|
||||||
|
#define io_trainbrains_h
|
||||||
|
|
||||||
|
#include "IO_GPIOBase.h"
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
/*
|
||||||
|
* IODevice subclass for trainbrains 3-block occupancy detector.
|
||||||
|
* For details see http://trainbrains.eu
|
||||||
|
*/
|
||||||
|
|
||||||
|
enum TrackUnoccupancy
|
||||||
|
{
|
||||||
|
TRACK_UNOCCUPANCY_UNKNOWN = 0,
|
||||||
|
TRACK_OCCUPIED = 1,
|
||||||
|
TRACK_UNOCCUPIED = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
class Trainbrains02 : public GPIOBase<uint16_t> {
|
||||||
|
public:
|
||||||
|
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress) {
|
||||||
|
if (checkNoOverlap(vpin, nPins, i2cAddress)) new Trainbrains02(vpin, nPins, i2cAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Constructor
|
||||||
|
Trainbrains02(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
||||||
|
: GPIOBase<uint16_t>((FSH *)F("Trainbrains02"), vpin, nPins, i2cAddress, interruptPin)
|
||||||
|
{
|
||||||
|
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||||
|
outputBuffer, sizeof(outputBuffer));
|
||||||
|
|
||||||
|
outputBuffer[0] = (uint8_t)_I2CAddress; // strips away the mux part.
|
||||||
|
outputBuffer[1] =14;
|
||||||
|
outputBuffer[2] =1;
|
||||||
|
outputBuffer[3] =0; // This is the channel updated at each poling call
|
||||||
|
outputBuffer[4] =0;
|
||||||
|
outputBuffer[5] =0;
|
||||||
|
outputBuffer[6] =0;
|
||||||
|
outputBuffer[7] =0;
|
||||||
|
outputBuffer[8] =0;
|
||||||
|
outputBuffer[9] =0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _writeGpioPort() override {}
|
||||||
|
|
||||||
|
void _readGpioPort(bool immediate) override {
|
||||||
|
// cycle channel on device each time
|
||||||
|
outputBuffer[3]=channelInProgress+1; // 1-origin
|
||||||
|
channelInProgress++;
|
||||||
|
if(channelInProgress>=_nPins) channelInProgress=0;
|
||||||
|
|
||||||
|
if (immediate) {
|
||||||
|
_processCompletion(I2CManager.read(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||||
|
outputBuffer, sizeof(outputBuffer)));
|
||||||
|
} 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) inputBuffer[6]=TRACK_UNOCCUPANCY_UNKNOWN;
|
||||||
|
if (inputBuffer[6] == TRACK_UNOCCUPIED ) _portInputState |= 0x01 <<channelInProgress;
|
||||||
|
else _portInputState &= ~(0x01 <<channelInProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t channelInProgress=0;
|
||||||
|
uint8_t outputBuffer[10];
|
||||||
|
uint8_t inputBuffer[10];
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
57
KeywordHasher.h
Normal file
57
KeywordHasher.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* © 2024 Vincent Hamp and Chris Harlow
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* Reader be aware:
|
||||||
|
This function implements the _hk data type so that a string keyword
|
||||||
|
is hashed to the same value as the DCCEXParser uses to hash incoming
|
||||||
|
keywords.
|
||||||
|
Thus "MAIN"_hk generates exactly the same run time vakue
|
||||||
|
as const int16_t HASH_KEYWORD_MAIN=11339
|
||||||
|
*/
|
||||||
|
#ifndef KeywordHAsher_h
|
||||||
|
#define KeywordHasher_h
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
constexpr uint16_t CompiletimeKeywordHasher(const char * sv, uint16_t running=0) {
|
||||||
|
return (*sv==0) ? running : CompiletimeKeywordHasher(sv+1,
|
||||||
|
(*sv >= '0' && *sv <= '9')
|
||||||
|
? (10*running+*sv-'0') // Numeric hash
|
||||||
|
: ((running << 5) + running) ^ *sv
|
||||||
|
); //
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr int16_t operator""_hk(const char * keyword, size_t len)
|
||||||
|
{
|
||||||
|
return (int16_t) CompiletimeKeywordHasher(keyword,len*0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Some historical values for testing:
|
||||||
|
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||||
|
const int16_t HASH_KEYWORD_SLOW = -17209;
|
||||||
|
const int16_t HASH_KEYWORD_SPEED28 = -17064;
|
||||||
|
const int16_t HASH_KEYWORD_SPEED128 = 25816;
|
||||||
|
*/
|
||||||
|
|
||||||
|
static_assert("MAIN"_hk == 11339,"Keyword hasher error");
|
||||||
|
static_assert("SLOW"_hk == -17209,"Keyword hasher error");
|
||||||
|
static_assert("SPEED28"_hk == -17064,"Keyword hasher error");
|
||||||
|
static_assert("SPEED128"_hk == 25816,"Keyword hasher error");
|
||||||
|
#endif
|
226
MotorDriver.cpp
226
MotorDriver.cpp
|
@ -1,9 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* © 2022-2023 Paul M Antoine
|
* © 2022-2024 Paul M Antoine
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2023 Harald Barth
|
* © 2020-2023 Harald Barth
|
||||||
* © 2020-2021 Chris Harlow
|
* © 2020-2021 Chris Harlow
|
||||||
|
* © 2023 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
|
@ -26,12 +27,20 @@
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#include "EXRAIL2.h"
|
||||||
|
|
||||||
unsigned long MotorDriver::globalOverloadStart = 0;
|
unsigned long MotorDriver::globalOverloadStart = 0;
|
||||||
|
|
||||||
volatile portreg_t shadowPORTA;
|
volatile portreg_t shadowPORTA;
|
||||||
volatile portreg_t shadowPORTB;
|
volatile portreg_t shadowPORTB;
|
||||||
volatile portreg_t shadowPORTC;
|
volatile portreg_t shadowPORTC;
|
||||||
|
#if defined(ARDUINO_ARCH_STM32)
|
||||||
|
volatile portreg_t shadowPORTD;
|
||||||
|
volatile portreg_t shadowPORTE;
|
||||||
|
volatile portreg_t shadowPORTF;
|
||||||
|
volatile portreg_t shadowPORTG;
|
||||||
|
volatile portreg_t shadowPORTH;
|
||||||
|
#endif
|
||||||
|
|
||||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
|
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) {
|
byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
|
||||||
|
@ -66,6 +75,31 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||||
fastSignalPin.inout = &shadowPORTC;
|
fastSignalPin.inout = &shadowPORTC;
|
||||||
}
|
}
|
||||||
|
if (HAVE_PORTD(fastSignalPin.inout == &PORTD)) {
|
||||||
|
DIAG(F("Found PORTD pin %d"),signalPin);
|
||||||
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||||
|
fastSignalPin.inout = &shadowPORTD;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTE(fastSignalPin.inout == &PORTE)) {
|
||||||
|
DIAG(F("Found PORTE pin %d"),signalPin);
|
||||||
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||||
|
fastSignalPin.inout = &shadowPORTE;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTF(fastSignalPin.inout == &PORTF)) {
|
||||||
|
DIAG(F("Found PORTF pin %d"),signalPin);
|
||||||
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||||
|
fastSignalPin.inout = &shadowPORTF;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTG(fastSignalPin.inout == &PORTG)) {
|
||||||
|
DIAG(F("Found PORTG pin %d"),signalPin);
|
||||||
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||||
|
fastSignalPin.inout = &shadowPORTG;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTH(fastSignalPin.inout == &PORTH)) {
|
||||||
|
DIAG(F("Found PORTH pin %d"),signalPin);
|
||||||
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||||
|
fastSignalPin.inout = &shadowPORTF;
|
||||||
|
}
|
||||||
|
|
||||||
signalPin2=signal_pin2;
|
signalPin2=signal_pin2;
|
||||||
if (signalPin2!=UNUSED_PIN) {
|
if (signalPin2!=UNUSED_PIN) {
|
||||||
|
@ -89,6 +123,31 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
fastSignalPin2.inout = &shadowPORTC;
|
fastSignalPin2.inout = &shadowPORTC;
|
||||||
}
|
}
|
||||||
|
if (HAVE_PORTD(fastSignalPin2.inout == &PORTD)) {
|
||||||
|
DIAG(F("Found PORTD pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTD;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTE(fastSignalPin2.inout == &PORTE)) {
|
||||||
|
DIAG(F("Found PORTE pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTE;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTF(fastSignalPin2.inout == &PORTF)) {
|
||||||
|
DIAG(F("Found PORTF pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTF;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTG(fastSignalPin2.inout == &PORTG)) {
|
||||||
|
DIAG(F("Found PORTG pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTG;
|
||||||
|
}
|
||||||
|
if (HAVE_PORTH(fastSignalPin2.inout == &PORTH)) {
|
||||||
|
DIAG(F("Found PORTH pin %d"),signalPin2);
|
||||||
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||||
|
fastSignalPin2.inout = &shadowPORTH;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else dualSignal=false;
|
else dualSignal=false;
|
||||||
|
|
||||||
|
@ -277,7 +336,7 @@ void MotorDriver::startCurrentFromHW() {
|
||||||
#pragma GCC pop_options
|
#pragma GCC pop_options
|
||||||
#endif //ANALOG_READ_INTERRUPT
|
#endif //ANALOG_READ_INTERRUPT
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||||
#ifdef VARIABLE_TONES
|
#ifdef VARIABLE_TONES
|
||||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||||
247, 262, 294, 330,
|
247, 262, 294, 330,
|
||||||
|
@ -288,49 +347,21 @@ uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||||
220, 196, 175, 165 };
|
220, 196, 175, 165 };
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
void MotorDriver::setDCSignal(byte speedcode) {
|
void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/) {
|
||||||
if (brakePin == UNUSED_PIN)
|
if (brakePin == UNUSED_PIN)
|
||||||
return;
|
return;
|
||||||
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 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
|
// spedcoode is a dcc speed & direction
|
||||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||||
byte tDir=speedcode & 0x80;
|
byte tDir=speedcode & 0x80;
|
||||||
byte brake;
|
byte brake;
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
{
|
if (tSpeed <= 1) brake = 255;
|
||||||
int f = 131;
|
else if (tSpeed >= 127) brake = 0;
|
||||||
|
else brake = 2 * (128-tSpeed);
|
||||||
|
|
||||||
|
{ // new block because of variable f
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||||
|
int f = frequency;
|
||||||
#ifdef VARIABLE_TONES
|
#ifdef VARIABLE_TONES
|
||||||
if (tSpeed > 2) {
|
if (tSpeed > 2) {
|
||||||
if (tSpeed <= 58) {
|
if (tSpeed <= 58) {
|
||||||
|
@ -338,19 +369,15 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
|
||||||
|
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake);
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
|
||||||
|
#else // all AVR here
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
|
||||||
|
analogWrite(brakePin, invertBrake ? 255-brake : brake);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
if (tSpeed <= 1) brake = 255;
|
|
||||||
else if (tSpeed >= 127) brake = 0;
|
|
||||||
else brake = 2 * (128-tSpeed);
|
|
||||||
if (invertBrake)
|
|
||||||
brake=255-brake;
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
|
||||||
#else
|
|
||||||
analogWrite(brakePin,brake);
|
|
||||||
#endif
|
|
||||||
//DIAG(F("DCSignal %d"), speedcode);
|
//DIAG(F("DCSignal %d"), speedcode);
|
||||||
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
|
@ -370,6 +397,36 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||||
setSignal(tDir);
|
setSignal(tDir);
|
||||||
HAVE_PORTC(PORTC=shadowPORTC);
|
HAVE_PORTC(PORTC=shadowPORTC);
|
||||||
interrupts();
|
interrupts();
|
||||||
|
} else if (HAVE_PORTD(fastSignalPin.shadowinout == &PORTD)) {
|
||||||
|
noInterrupts();
|
||||||
|
HAVE_PORTD(shadowPORTD=PORTD);
|
||||||
|
setSignal(tDir);
|
||||||
|
HAVE_PORTD(PORTD=shadowPORTD);
|
||||||
|
interrupts();
|
||||||
|
} else if (HAVE_PORTE(fastSignalPin.shadowinout == &PORTE)) {
|
||||||
|
noInterrupts();
|
||||||
|
HAVE_PORTE(shadowPORTE=PORTE);
|
||||||
|
setSignal(tDir);
|
||||||
|
HAVE_PORTE(PORTE=shadowPORTE);
|
||||||
|
interrupts();
|
||||||
|
} else if (HAVE_PORTF(fastSignalPin.shadowinout == &PORTF)) {
|
||||||
|
noInterrupts();
|
||||||
|
HAVE_PORTF(shadowPORTF=PORTF);
|
||||||
|
setSignal(tDir);
|
||||||
|
HAVE_PORTF(PORTF=shadowPORTF);
|
||||||
|
interrupts();
|
||||||
|
} else if (HAVE_PORTG(fastSignalPin.shadowinout == &PORTG)) {
|
||||||
|
noInterrupts();
|
||||||
|
HAVE_PORTG(shadowPORTG=PORTG);
|
||||||
|
setSignal(tDir);
|
||||||
|
HAVE_PORTG(PORTG=shadowPORTG);
|
||||||
|
interrupts();
|
||||||
|
} else if (HAVE_PORTH(fastSignalPin.shadowinout == &PORTH)) {
|
||||||
|
noInterrupts();
|
||||||
|
HAVE_PORTH(shadowPORTH=PORTH);
|
||||||
|
setSignal(tDir);
|
||||||
|
HAVE_PORTH(PORTH=shadowPORTH);
|
||||||
|
interrupts();
|
||||||
} else {
|
} else {
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
setSignal(tDir);
|
setSignal(tDir);
|
||||||
|
@ -379,53 +436,28 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||||
void MotorDriver::throttleInrush(bool on) {
|
void MotorDriver::throttleInrush(bool on) {
|
||||||
if (brakePin == UNUSED_PIN)
|
if (brakePin == UNUSED_PIN)
|
||||||
return;
|
return;
|
||||||
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
|
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT | TRACK_MODE_BOOST)))
|
||||||
return;
|
return;
|
||||||
byte duty = on ? 208 : 0;
|
byte duty = on ? 207 : 0; // duty of 81% at 62500Hz this gives pauses of 3usec
|
||||||
if (invertBrake)
|
|
||||||
duty = 255-duty;
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
if(on) {
|
if(on) {
|
||||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
DCCTimer::DCCEXInrushControlOn(brakePin, duty, invertBrake);
|
||||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
|
||||||
} else {
|
} else {
|
||||||
ledcDetachPin(brakePin);
|
ledcDetachPin(brakePin); // not DCCTimer::DCCEXledcDetachPin() as we have not
|
||||||
|
// registered the pin in the pin to channel array
|
||||||
}
|
}
|
||||||
#else
|
#elif defined(ARDUINO_ARCH_STM32)
|
||||||
if(on) {
|
if(on) {
|
||||||
switch(brakePin) {
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
||||||
#if defined(ARDUINO_AVR_UNO)
|
DCCTimer::DCCEXanalogWrite(brakePin,duty,invertBrake);
|
||||||
// Not worth doin something here as:
|
} else {
|
||||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
pinMode(brakePin, OUTPUT);
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
|
#else // all AVR here
|
||||||
|
if (invertBrake)
|
||||||
|
duty = 255-duty;
|
||||||
|
if(on){
|
||||||
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
||||||
}
|
}
|
||||||
analogWrite(brakePin,duty);
|
analogWrite(brakePin,duty);
|
||||||
#endif
|
#endif
|
||||||
|
@ -543,6 +575,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||||
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
||||||
}
|
}
|
||||||
setPower(POWERMODE::ALERT);
|
setPower(POWERMODE::ALERT);
|
||||||
|
if ((trackMode & TRACK_MODE_AUTOINV) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){
|
||||||
|
DIAG(F("TRACK %c INVERT"), trackno + 'A');
|
||||||
|
invertOutput();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
// all well
|
// all well
|
||||||
|
@ -602,6 +638,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||||
}
|
}
|
||||||
throttleInrush(false);
|
throttleInrush(false);
|
||||||
setPower(POWERMODE::ON);
|
setPower(POWERMODE::ON);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (goodtime > POWER_SAMPLE_ALERT_GOOD/2) {
|
||||||
|
throttleInrush(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -614,6 +654,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||||
power_sample_overload_wait *= 2;
|
power_sample_overload_wait *= 2;
|
||||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||||
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
DIAG(F("Calling EXRAIL"));
|
||||||
|
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
|
||||||
|
#endif
|
||||||
// power on test
|
// power on test
|
||||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||||
setPower(POWERMODE::ALERT);
|
setPower(POWERMODE::ALERT);
|
||||||
|
|
115
MotorDriver.h
115
MotorDriver.h
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* © 2022 Paul M Antoine
|
* © 2022-2024 Paul M. Antoine
|
||||||
* © 2021 Mike S
|
* © 2021 Mike S
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020 Chris Harlow
|
* © 2020 Chris Harlow
|
||||||
* © 2022 Harald Barth
|
* © 2022,2023 Harald Barth
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
|
@ -26,10 +26,24 @@
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "IODevice.h"
|
#include "IODevice.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
|
#include <wiring_private.h>
|
||||||
|
|
||||||
// use powers of two so we can do logical and/or on the track modes in if clauses.
|
// use powers of two so we can do logical and/or on the track modes in if clauses.
|
||||||
|
// RACK_MODE_DCX is (TRACK_MODE_DC|TRACK_MODE_INV)
|
||||||
|
template<class T> inline T operator~ (T a) { return (T)~(int)a; }
|
||||||
|
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,
|
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};
|
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 setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||||
|
@ -60,6 +74,24 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||||
#define HAVE_PORTB(X) X
|
#define HAVE_PORTB(X) X
|
||||||
#define PORTC GPIOC->ODR
|
#define PORTC GPIOC->ODR
|
||||||
#define HAVE_PORTC(X) X
|
#define HAVE_PORTC(X) X
|
||||||
|
#define PORTD GPIOD->ODR
|
||||||
|
#define HAVE_PORTD(X) X
|
||||||
|
#if defined(GPIOE)
|
||||||
|
#define PORTE GPIOE->ODR
|
||||||
|
#define HAVE_PORTE(X) X
|
||||||
|
#endif
|
||||||
|
#if defined(GPIOF)
|
||||||
|
#define PORTF GPIOF->ODR
|
||||||
|
#define HAVE_PORTF(X) X
|
||||||
|
#endif
|
||||||
|
#if defined(GPIOG)
|
||||||
|
#define PORTG GPIOG->ODR
|
||||||
|
#define HAVE_PORTG(X) X
|
||||||
|
#endif
|
||||||
|
#if defined(GPIOH)
|
||||||
|
#define PORTH GPIOH->ODR
|
||||||
|
#define HAVE_PORTH(X) X
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// if macros not defined as pass-through we define
|
// if macros not defined as pass-through we define
|
||||||
|
@ -74,6 +106,21 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||||
#ifndef HAVE_PORTC
|
#ifndef HAVE_PORTC
|
||||||
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||||
#endif
|
#endif
|
||||||
|
#ifndef HAVE_PORTD
|
||||||
|
#define HAVE_PORTD(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||||
|
#endif
|
||||||
|
#ifndef HAVE_PORTE
|
||||||
|
#define HAVE_PORTE(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||||
|
#endif
|
||||||
|
#ifndef HAVE_PORTF
|
||||||
|
#define HAVE_PORTF(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||||
|
#endif
|
||||||
|
#ifndef HAVE_PORTG
|
||||||
|
#define HAVE_PORTG(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
|
// Virtualised Motor shield 1-track hardware Interface
|
||||||
|
|
||||||
|
@ -110,6 +157,11 @@ struct FASTPIN {
|
||||||
extern volatile portreg_t shadowPORTA;
|
extern volatile portreg_t shadowPORTA;
|
||||||
extern volatile portreg_t shadowPORTB;
|
extern volatile portreg_t shadowPORTB;
|
||||||
extern volatile portreg_t shadowPORTC;
|
extern volatile portreg_t shadowPORTC;
|
||||||
|
extern volatile portreg_t shadowPORTD;
|
||||||
|
extern volatile portreg_t shadowPORTE;
|
||||||
|
extern volatile portreg_t shadowPORTF;
|
||||||
|
extern volatile portreg_t shadowPORTG;
|
||||||
|
extern volatile portreg_t shadowPORTH;
|
||||||
|
|
||||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
|
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
|
||||||
|
|
||||||
|
@ -127,6 +179,10 @@ class MotorDriver {
|
||||||
// from outside interrupt
|
// from outside interrupt
|
||||||
void setBrake( bool on, bool interruptContext=false);
|
void setBrake( bool on, bool interruptContext=false);
|
||||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||||
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
|
if (invertPhase)
|
||||||
|
high = !high;
|
||||||
|
#endif
|
||||||
if (trackPWM) {
|
if (trackPWM) {
|
||||||
DCCTimer::setPWM(signalPin,high);
|
DCCTimer::setPWM(signalPin,high);
|
||||||
}
|
}
|
||||||
|
@ -146,15 +202,22 @@ class MotorDriver {
|
||||||
pinMode(signalPin, OUTPUT);
|
pinMode(signalPin, OUTPUT);
|
||||||
else
|
else
|
||||||
pinMode(signalPin, INPUT);
|
pinMode(signalPin, INPUT);
|
||||||
|
if (signalPin2 != UNUSED_PIN) {
|
||||||
|
if (on)
|
||||||
|
pinMode(signalPin2, OUTPUT);
|
||||||
|
else
|
||||||
|
pinMode(signalPin2, INPUT);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||||
void setDCSignal(byte speedByte);
|
inline int8_t getBrakePinSigned() { return invertBrake ? -brakePin : brakePin; };
|
||||||
|
void setDCSignal(byte speedByte, uint8_t frequency=0);
|
||||||
void throttleInrush(bool on);
|
void throttleInrush(bool on);
|
||||||
inline void detachDCSignal() {
|
inline void detachDCSignal() {
|
||||||
#if defined(__arm__)
|
#if defined(__arm__)
|
||||||
pinMode(brakePin, OUTPUT);
|
pinMode(brakePin, OUTPUT);
|
||||||
#elif defined(ARDUINO_ARCH_ESP32)
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
ledcDetachPin(brakePin);
|
DCCTimer::DCCEXledcDetachPin(brakePin);
|
||||||
#else
|
#else
|
||||||
setDCSignal(128);
|
setDCSignal(128);
|
||||||
#endif
|
#endif
|
||||||
|
@ -163,16 +226,16 @@ class MotorDriver {
|
||||||
unsigned int raw2mA( int raw);
|
unsigned int raw2mA( int raw);
|
||||||
unsigned int mA2raw( unsigned int mA);
|
unsigned int mA2raw( unsigned int mA);
|
||||||
inline bool brakeCanPWM() {
|
inline bool brakeCanPWM() {
|
||||||
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
|
return (brakePin != UNUSED_PIN); // This was just (true) but we probably do need to check for UNUSED_PIN!
|
||||||
return true;
|
#elif defined(__arm__)
|
||||||
#else
|
// On ARM we can use digitalPinHasPWM
|
||||||
#ifdef digitalPinToTimer
|
return ((brakePin!=UNUSED_PIN) && (digitalPinHasPWM(brakePin)));
|
||||||
|
#elif defined(digitalPinToTimer)
|
||||||
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
|
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
|
||||||
#else
|
#else
|
||||||
return (brakePin<14 && brakePin >1);
|
return (brakePin<14 && brakePin >1);
|
||||||
#endif //digitalPinToTimer
|
#endif
|
||||||
#endif //ESP32/ARM
|
|
||||||
}
|
}
|
||||||
inline int getRawCurrentTripValue() {
|
inline int getRawCurrentTripValue() {
|
||||||
return rawCurrentTripValue;
|
return rawCurrentTripValue;
|
||||||
|
@ -210,6 +273,32 @@ class MotorDriver {
|
||||||
#endif
|
#endif
|
||||||
inline void setMode(TRACK_MODE m) {
|
inline void setMode(TRACK_MODE m) {
|
||||||
trackMode = m;
|
trackMode = m;
|
||||||
|
invertOutput(trackMode & TRACK_MODE_INV);
|
||||||
|
};
|
||||||
|
inline void invertOutput() { // toggles output inversion
|
||||||
|
invertPhase = !invertPhase;
|
||||||
|
invertOutput(invertPhase);
|
||||||
|
};
|
||||||
|
inline void invertOutput(bool b) { // sets output inverted or not
|
||||||
|
if (b)
|
||||||
|
invertPhase = 1;
|
||||||
|
else
|
||||||
|
invertPhase = 0;
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
pinpair p = getSignalPin();
|
||||||
|
uint32_t *outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.pin);
|
||||||
|
if (invertPhase) // set or clear the invert bit in the gpio out register
|
||||||
|
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||||
|
else
|
||||||
|
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||||
|
if (p.invpin != UNUSED_PIN) {
|
||||||
|
outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.invpin);
|
||||||
|
if (invertPhase) // clear or set the invert bit in the gpio out register
|
||||||
|
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||||
|
else
|
||||||
|
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
inline TRACK_MODE getMode() {
|
inline TRACK_MODE getMode() {
|
||||||
return trackMode;
|
return trackMode;
|
||||||
|
@ -241,7 +330,7 @@ class MotorDriver {
|
||||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||||
bool invertPower; // power pin passed as negative means pin is inverted
|
bool invertPower; // power pin passed as negative means pin is inverted
|
||||||
bool invertFault; // fault pin passed as negative means pin is inverted
|
bool invertFault; // fault pin passed as negative means pin is inverted
|
||||||
|
bool invertPhase = 0; // phase of out pin is inverted
|
||||||
// Raw to milliamp conversion factors avoiding float data types.
|
// Raw to milliamp conversion factors avoiding float data types.
|
||||||
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
||||||
//
|
//
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* © 2022-2023 Paul M. Antoine
|
* © 2022-2023 Paul M. Antoine
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2023 Harald Barth
|
* © 2020-2024 Harald Barth
|
||||||
* (c) 2020 Chris Harlow. All rights reserved.
|
* (c) 2020 Chris Harlow. All rights reserved.
|
||||||
* (c) 2021 Fred Decker. All rights reserved.
|
* (c) 2021 Fred Decker. All rights reserved.
|
||||||
* (c) 2020 Harald Barth. All rights reserved.
|
* (c) 2020 Harald Barth. All rights reserved.
|
||||||
|
@ -57,6 +57,10 @@
|
||||||
// of the brake pin on the motor bridge is inverted
|
// of the brake pin on the motor bridge is inverted
|
||||||
// (HIGH == release brake)
|
// (HIGH == release brake)
|
||||||
|
|
||||||
|
// You can have a CS wihout any possibility to do any track signal.
|
||||||
|
// That's strange but possible.
|
||||||
|
#define NO_SHIELD F("No shield at all")
|
||||||
|
|
||||||
// Arduino STANDARD Motor Shield, used on different architectures:
|
// Arduino STANDARD Motor Shield, used on different architectures:
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
# My (FranziHH) DCC++ Ex Hardware
|
||||||
|
<img src="/images/IMG_5870_1.jpg" height="400px" title="DCC++ Ex Hardware">
|
||||||
|
|
||||||
|
|
||||||
# What is DCC-EX?
|
# What is DCC-EX?
|
||||||
DCC-EX is a team of dedicated enthusiasts producing open source DCC & DC solutions for you to run your complete model railroad layout. Our easy to use, do-it-yourself, and free open source products run on off-the-shelf Arduino technology and are supported by numerous third party hardware and apps like JMRI, Engine Driver, wiThrottle, Rocrail and more.
|
DCC-EX is a team of dedicated enthusiasts producing open source DCC & DC solutions for you to run your complete model railroad layout. Our easy to use, do-it-yourself, and free open source products run on off-the-shelf Arduino technology and are supported by numerous third party hardware and apps like JMRI, Engine Driver, wiThrottle, Rocrail and more.
|
||||||
|
|
||||||
|
|
BIN
README.pdf
Normal file
BIN
README.pdf
Normal file
Binary file not shown.
119
Release_Notes/Exrail mods.txt
Normal file
119
Release_Notes/Exrail mods.txt
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
// 5.2.49
|
||||||
|
|
||||||
|
Which is a more efficient than the AT/AFTER/IF methods
|
||||||
|
of handling buttons and switches, especially on MIMIC panels.
|
||||||
|
|
||||||
|
ONBUTTON(vpin)
|
||||||
|
handles debounce and starts a task if a button is used to
|
||||||
|
short a pin to ground.
|
||||||
|
|
||||||
|
for example:
|
||||||
|
ONBUTTON(30) TOGGLE_TURNOUT(30) DONE
|
||||||
|
|
||||||
|
ONSENSOR(vpin)
|
||||||
|
handles debounce and starts a task if the pin changes.
|
||||||
|
You may want to check the pin state with an IF ...
|
||||||
|
|
||||||
|
Note the ONBUTTON and ONSENSOR are not generally useful
|
||||||
|
for track sensors and running trains, because you dont know which
|
||||||
|
train triggered the sensor.
|
||||||
|
|
||||||
|
// 5.2.47
|
||||||
|
|
||||||
|
BLINK(vpin, onMs,offMs)
|
||||||
|
|
||||||
|
which will start a vpin blinking until such time as it is SET, RESET or set by a signal operation such as RED, AMBER, GREEN.
|
||||||
|
|
||||||
|
BLINK returns immediately, the blinking is autonomous.
|
||||||
|
|
||||||
|
This means a signal that always blinks amber could be done like this:
|
||||||
|
|
||||||
|
SIGNAL(30,31,32)
|
||||||
|
ONAMBER(30) BLINK(31,500,500) DONE
|
||||||
|
|
||||||
|
The RED or GREEN calls will turn off the amber blink automatically.
|
||||||
|
|
||||||
|
Alternatively a signal that has normal AMBER and flashing AMBER could be like this:
|
||||||
|
|
||||||
|
#define FLASHAMBER(signal) \
|
||||||
|
AMBER(signal) \
|
||||||
|
BLINK(signal+1,500,500)
|
||||||
|
|
||||||
|
(Caution: this assumes that the amber pin is redpin+1)
|
||||||
|
|
||||||
|
==
|
||||||
|
|
||||||
|
FTOGGLE(function)
|
||||||
|
Toggles the current loco function (see FON and FOFF)
|
||||||
|
|
||||||
|
XFTOGGLE(loco,function)
|
||||||
|
Toggles the function on given loco. (See XFON, XFOFF)
|
||||||
|
|
||||||
|
TOGGLE_TURNOUT(id)
|
||||||
|
Toggles the turnout (see CLOSE, THROW)
|
||||||
|
|
||||||
|
STEALTH_GLOBAL(code)
|
||||||
|
ADVANCED C++ users only.
|
||||||
|
Inserts code such as static variables and functions that
|
||||||
|
may be utilised by multiple STEALTH operations.
|
||||||
|
|
||||||
|
|
||||||
|
// 5.2.34 - <A address aspect> Command fopr DCC Extended Accessories.
|
||||||
|
This command sends an extended accessory packet to the track, Normally used to set
|
||||||
|
a signal aspect. Aspect numbers are undefined as sdtandards except for 0 which is
|
||||||
|
always considered a stop.
|
||||||
|
|
||||||
|
// - Exrail ASPECT(address,aspect) for above.
|
||||||
|
The ASPECT command sents an aspect to a DCC accessory using the same logic as
|
||||||
|
<A aspect address>.
|
||||||
|
|
||||||
|
// - EXRAIL DCCX_SIGNAL(Address,redAspect,amberAspect,greenAspect)
|
||||||
|
This defines a signal (with id same as dcc address) that can be operated
|
||||||
|
by the RED/AMBER/GREEN commands. In each case the command uses the signal
|
||||||
|
address to refer to the signal and the aspect chosen depends on the use of the RED
|
||||||
|
AMBER or GREEN command sent. Other aspects may be sent but will require the
|
||||||
|
direct use of the ASPECT command.
|
||||||
|
The IFRED/IFAMBER/IFGREEN and ONRED/ONAMBER/ONGREEN commands contunue to operate
|
||||||
|
as for any other signal type. It is important to be aware that use of the ASPECT
|
||||||
|
or <A> commands will correctly set the IF flags and call the ON handlers if ASPECT
|
||||||
|
is used to set one of the three aspects defined in the DCCX_SIGNAL command.
|
||||||
|
Direct use of other aspects does not affect the signal flags.
|
||||||
|
ASPECT and <A> can be used withput defining any signal if tyhe flag management or
|
||||||
|
ON event handlers are not required.
|
||||||
|
|
||||||
|
// 5.2.33 - Exrail CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||||
|
This macro offsers a more convenient way of performing the HAL call in halSetup.h
|
||||||
|
In halSetup.h --- IODevice::configureServo(101,300,400,PCA9685::slow);
|
||||||
|
In myAutomation.h --- CONFIGURE_SERVO(101,300,400,slow)
|
||||||
|
|
||||||
|
// 5.2.32 - Railcom Cutout (Initial trial Mega2560 only)
|
||||||
|
This cutout will only work on a Mega2560 with a single EX8874 motor shield
|
||||||
|
configured in the normal way with the main track brake pin on pin 9.
|
||||||
|
<C RAILCOM ON> Turns on the cutout mechanism.
|
||||||
|
<C RAILCOM OFF> Tirns off the cutout. (This is the default)
|
||||||
|
<C RAILCOM DEBUG> ONLY to be used by developers used for waveform diagnostics.
|
||||||
|
(In DEBUG mode the main track idle packets are replaced with reset packets, This
|
||||||
|
makes it far easier to see the preambles and cutouts on a logic analyser or scope.)
|
||||||
|
|
||||||
|
// 5.2.31 - Exrail JMRI_SENSOR(vpin [,count]) creates <S> types.
|
||||||
|
This Macro causes the creation of JMRI <S> type sensors in a way that is
|
||||||
|
simpler than repeating lines of <S> commands.
|
||||||
|
JMRI_SENSOR(100) is equenvelant to <S 100 100 1>
|
||||||
|
JMRI_SENSOR(100,16) will create <S> type sensors for vpins 100-115.
|
||||||
|
|
||||||
|
// 5.2.26 - Silently ignore overridden HAL defaults
|
||||||
|
// - include HAL_IGNORE_DEFAULTS macro in EXRAIL
|
||||||
|
The HAL_IGNORE_DEFAULTS command, anywhere in myAutomation.h will
|
||||||
|
prevent the startup code from trying the default I2C sensors/servos.
|
||||||
|
// 5.2.24 - Exrail macro asserts to catch
|
||||||
|
// : duplicate/missing automation/route/sequence/call ids
|
||||||
|
// : latches and reserves out of range
|
||||||
|
// : speeds out of range
|
||||||
|
Causes compiler time messages for EXRAIL issues that would normally
|
||||||
|
only be discovered by things going wrong at run time.
|
||||||
|
// 5.2.13 - EXRAIL STEALTH
|
||||||
|
Permits a certain level of C++ code to be embedded as a single step in
|
||||||
|
an exrail sequence. Serious engineers only.
|
||||||
|
|
||||||
|
// 5.2.9 - EXRAIL STASH feature
|
||||||
|
// - Added ROUTE_DISABLED macro in EXRAIL
|
|
@ -230,6 +230,13 @@ Sensor *Sensor::create(int snum, VPIN pin, int pullUp){
|
||||||
return tt;
|
return tt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creet multiple eponymous sensors based on vpin alone.
|
||||||
|
void Sensor::createMultiple(VPIN firstPin, byte count) {
|
||||||
|
for (byte i=0;i<count;i++) {
|
||||||
|
create(firstPin+i,firstPin+i,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// Object method to directly change the input state, for sensors such as LCN which are updated
|
// Object method to directly change the input state, for sensors such as LCN which are updated
|
||||||
// by means other than by polling an input.
|
// by means other than by polling an input.
|
||||||
|
|
|
@ -76,6 +76,7 @@ public:
|
||||||
static void store();
|
static void store();
|
||||||
#endif
|
#endif
|
||||||
static Sensor *create(int id, VPIN vpin, int pullUp);
|
static Sensor *create(int id, VPIN vpin, int pullUp);
|
||||||
|
static void createMultiple(VPIN firstPin, byte count=1);
|
||||||
static Sensor* get(int id);
|
static Sensor* get(int id);
|
||||||
static bool remove(int id);
|
static bool remove(int id);
|
||||||
static void checkAll();
|
static void checkAll();
|
||||||
|
|
|
@ -111,14 +111,15 @@ void SerialManager::loop2() {
|
||||||
bufferLength = 0;
|
bufferLength = 0;
|
||||||
buffer[0] = '\0';
|
buffer[0] = '\0';
|
||||||
}
|
}
|
||||||
else if (ch == '>') {
|
else if (inCommandPayload) {
|
||||||
|
if (bufferLength < (COMMAND_BUFFER_SIZE-1))
|
||||||
|
buffer[bufferLength++] = ch;
|
||||||
|
if (ch == '>') {
|
||||||
buffer[bufferLength] = '\0';
|
buffer[bufferLength] = '\0';
|
||||||
DCCEXParser::parse(serial, buffer, NULL);
|
DCCEXParser::parse(serial, buffer, NULL);
|
||||||
inCommandPayload = false;
|
inCommandPayload = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (inCommandPayload) {
|
|
||||||
if (bufferLength < (COMMAND_BUFFER_SIZE-1)) buffer[bufferLength++] = ch;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include "DisplayInterface.h"
|
#include "DisplayInterface.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
|
||||||
bool Diag::ACK=false;
|
bool Diag::ACK=false;
|
||||||
bool Diag::CMD=false;
|
bool Diag::CMD=false;
|
||||||
|
@ -38,13 +39,28 @@ void StringFormatter::diag( const FSH* input...) {
|
||||||
|
|
||||||
void StringFormatter::lcd(byte row, const FSH* input...) {
|
void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
#ifndef DISABLE_VDPY
|
||||||
|
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(0,row);
|
||||||
|
#else
|
||||||
|
Print * virtualLCD=NULL;
|
||||||
|
#endif
|
||||||
// Issue the LCD as a diag first
|
// Issue the LCD as a diag first
|
||||||
|
// Unless the same serial is asking for the virtual @ respomnse
|
||||||
|
if (virtualLCD!=&USB_SERIAL) {
|
||||||
send(&USB_SERIAL,F("<* LCD%d:"),row);
|
send(&USB_SERIAL,F("<* LCD%d:"),row);
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(&USB_SERIAL,input,args);
|
send2(&USB_SERIAL,input,args);
|
||||||
send(&USB_SERIAL,F(" *>\n"));
|
send(&USB_SERIAL,F(" *>\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef DISABLE_VDPY
|
||||||
|
// send to virtual LCD collector (if any)
|
||||||
|
if (virtualLCD) {
|
||||||
|
va_start(args, input);
|
||||||
|
send2(virtualLCD,input,args);
|
||||||
|
CommandDistributor::commitVirtualLCDSerial();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
DisplayInterface::setRow(row);
|
DisplayInterface::setRow(row);
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(DisplayInterface::getDisplayHandler(),input,args);
|
send2(DisplayInterface::getDisplayHandler(),input,args);
|
||||||
|
@ -53,6 +69,16 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||||
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
|
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
||||||
|
// send to virtual LCD collector (if any)
|
||||||
|
#ifndef DISABLE_VDPY
|
||||||
|
Print * virtualLCD=CommandDistributor::getVirtualLCDSerial(display,row);
|
||||||
|
if (virtualLCD) {
|
||||||
|
va_start(args, input);
|
||||||
|
send2(virtualLCD,input,args);
|
||||||
|
CommandDistributor::commitVirtualLCDSerial();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
DisplayInterface::setRow(display, row);
|
DisplayInterface::setRow(display, row);
|
||||||
va_start(args, input);
|
va_start(args, input);
|
||||||
send2(DisplayInterface::getDisplayHandler(),input,args);
|
send2(DisplayInterface::getDisplayHandler(),input,args);
|
||||||
|
@ -117,6 +143,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
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 int)va_arg(args, unsigned int), HEX); break;
|
||||||
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
|
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
|
||||||
|
case 'h': printHex(stream,(unsigned int)va_arg(args, unsigned int)); break;
|
||||||
case 'M':
|
case 'M':
|
||||||
{ // this prints a unsigned long microseconds time in readable format
|
{ // this prints a unsigned long microseconds time in readable format
|
||||||
unsigned long time = va_arg(args, long);
|
unsigned long time = va_arg(args, long);
|
||||||
|
@ -218,4 +245,14 @@ void StringFormatter::printPadded(Print* stream, long value, byte width, bool fo
|
||||||
if (!formatLeft) stream->print(value, DEC);
|
if (!formatLeft) stream->print(value, DEC);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// printHex prints the full 2 byte hex with leading zeros, unlike print(value,HEX)
|
||||||
|
const char FLASH hexchars[]="0123456789ABCDEF";
|
||||||
|
void StringFormatter::printHex(Print * stream,uint16_t value) {
|
||||||
|
char result[5];
|
||||||
|
for (int i=3;i>=0;i--) {
|
||||||
|
result[i]=GETFLASH(hexchars+(value & 0x0F));
|
||||||
|
value>>=4;
|
||||||
|
}
|
||||||
|
result[4]='\0';
|
||||||
|
stream->print(result);
|
||||||
|
}
|
||||||
|
|
|
@ -49,10 +49,10 @@ class StringFormatter
|
||||||
static void lcd2(uint8_t display, byte row, const FSH* input...);
|
static void lcd2(uint8_t display, byte row, const FSH* input...);
|
||||||
static void printEscapes(char * input);
|
static void printEscapes(char * input);
|
||||||
static void printEscape( char c);
|
static void printEscape( char c);
|
||||||
|
static void printHex(Print * stream,uint16_t value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void send2(Print * serial, const FSH* input,va_list args);
|
static void send2(Print * serial, const FSH* input,va_list args);
|
||||||
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
|
static void printPadded(Print* stream, long value, byte width, bool formatLeft);
|
||||||
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
397
TrackManager.cpp
397
TrackManager.cpp
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* © 2022 Chris Harlow
|
* © 2022 Chris Harlow
|
||||||
* © 2022 Harald Barth
|
* © 2022-2024 Harald Barth
|
||||||
|
* © 2023 Colin Murdoch
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of DCC++EX
|
* This file is part of DCC++EX
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#include "defines.h"
|
||||||
#include "TrackManager.h"
|
#include "TrackManager.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
|
@ -26,29 +28,20 @@
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "CommandDistributor.h"
|
#include "CommandDistributor.h"
|
||||||
|
#include "DCCEXParser.h"
|
||||||
|
#include "KeywordHasher.h"
|
||||||
// Virtualised Motor shield multi-track hardware Interface
|
// Virtualised Motor shield multi-track hardware Interface
|
||||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||||
|
|
||||||
#define APPLY_BY_MODE(findmode,function) \
|
#define APPLY_BY_MODE(findmode,function) \
|
||||||
FOR_EACH_TRACK(t) \
|
FOR_EACH_TRACK(t) \
|
||||||
if (track[t]->getMode()==findmode) \
|
if (track[t]->getMode() & findmode) \
|
||||||
track[t]->function;
|
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];
|
MotorDriver * TrackManager::track[MAX_TRACKS] = { NULL };
|
||||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
|
int16_t TrackManager::trackDCAddr[MAX_TRACKS] = { 0 };
|
||||||
|
|
||||||
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
|
int8_t TrackManager::lastTrack=-1;
|
||||||
byte TrackManager::lastTrack=0;
|
|
||||||
bool TrackManager::progTrackSyncMain=false;
|
bool TrackManager::progTrackSyncMain=false;
|
||||||
bool TrackManager::progTrackBoosted=false;
|
bool TrackManager::progTrackBoosted=false;
|
||||||
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
int16_t TrackManager::joinRelay=UNUSED_PIN;
|
||||||
|
@ -85,7 +78,7 @@ void TrackManager::sampleCurrent() {
|
||||||
if (!waiting) {
|
if (!waiting) {
|
||||||
// look for a valid track to sample or until we are around
|
// look for a valid track to sample or until we are around
|
||||||
while (true) {
|
while (true) {
|
||||||
if (track[tr]->getMode() & ( 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_BOOST|TRACK_MODE_EXT )) {
|
||||||
track[tr]->startCurrentFromHW();
|
track[tr]->startCurrentFromHW();
|
||||||
// for scope debug track[1]->setBrake(1);
|
// for scope debug track[1]->setBrake(1);
|
||||||
waiting = true;
|
waiting = true;
|
||||||
|
@ -153,16 +146,16 @@ void TrackManager::setDCCSignal( bool on) {
|
||||||
HAVE_PORTA(shadowPORTA=PORTA);
|
HAVE_PORTA(shadowPORTA=PORTA);
|
||||||
HAVE_PORTB(shadowPORTB=PORTB);
|
HAVE_PORTB(shadowPORTB=PORTB);
|
||||||
HAVE_PORTC(shadowPORTC=PORTC);
|
HAVE_PORTC(shadowPORTC=PORTC);
|
||||||
|
HAVE_PORTD(shadowPORTD=PORTD);
|
||||||
|
HAVE_PORTE(shadowPORTE=PORTE);
|
||||||
|
HAVE_PORTF(shadowPORTF=PORTF);
|
||||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||||
HAVE_PORTA(PORTA=shadowPORTA);
|
HAVE_PORTA(PORTA=shadowPORTA);
|
||||||
HAVE_PORTB(PORTB=shadowPORTB);
|
HAVE_PORTB(PORTB=shadowPORTB);
|
||||||
HAVE_PORTC(PORTC=shadowPORTC);
|
HAVE_PORTC(PORTC=shadowPORTC);
|
||||||
}
|
HAVE_PORTD(PORTD=shadowPORTD);
|
||||||
|
HAVE_PORTE(PORTE=shadowPORTE);
|
||||||
void TrackManager::setCutout( bool on) {
|
HAVE_PORTF(PORTF=shadowPORTF);
|
||||||
(void) on;
|
|
||||||
// TODO Cutout needs fake ports as well
|
|
||||||
// TODO APPLY_BY_MODE(TRACK_MODE_MAIN,setCutout(on));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// setPROGSignal(), called from interrupt context
|
// setPROGSignal(), called from interrupt context
|
||||||
|
@ -171,10 +164,16 @@ void TrackManager::setPROGSignal( bool on) {
|
||||||
HAVE_PORTA(shadowPORTA=PORTA);
|
HAVE_PORTA(shadowPORTA=PORTA);
|
||||||
HAVE_PORTB(shadowPORTB=PORTB);
|
HAVE_PORTB(shadowPORTB=PORTB);
|
||||||
HAVE_PORTC(shadowPORTC=PORTC);
|
HAVE_PORTC(shadowPORTC=PORTC);
|
||||||
|
HAVE_PORTD(shadowPORTD=PORTD);
|
||||||
|
HAVE_PORTE(shadowPORTE=PORTE);
|
||||||
|
HAVE_PORTF(shadowPORTF=PORTF);
|
||||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||||
HAVE_PORTA(PORTA=shadowPORTA);
|
HAVE_PORTA(PORTA=shadowPORTA);
|
||||||
HAVE_PORTB(PORTB=shadowPORTB);
|
HAVE_PORTB(PORTB=shadowPORTB);
|
||||||
HAVE_PORTC(PORTC=shadowPORTC);
|
HAVE_PORTC(PORTC=shadowPORTC);
|
||||||
|
HAVE_PORTD(PORTD=shadowPORTD);
|
||||||
|
HAVE_PORTE(PORTE=shadowPORTE);
|
||||||
|
HAVE_PORTF(PORTF=shadowPORTF);
|
||||||
}
|
}
|
||||||
|
|
||||||
// setDCSignal(), called from normal context
|
// setDCSignal(), called from normal context
|
||||||
|
@ -183,17 +182,20 @@ void TrackManager::setPROGSignal( bool on) {
|
||||||
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||||
FOR_EACH_TRACK(t) {
|
FOR_EACH_TRACK(t) {
|
||||||
if (trackDCAddr[t]!=cab && cab != 0) continue;
|
if (trackDCAddr[t]!=cab && cab != 0) continue;
|
||||||
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
if (track[t]->getMode() & TRACK_MODE_DC)
|
||||||
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
track[t]->setDCSignal(speedbyte, DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||||
|
|
||||||
|
// Remember track mode we came from for later
|
||||||
|
TRACK_MODE oldmode = track[trackToSet]->getMode();
|
||||||
|
|
||||||
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
|
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
|
||||||
// DC tracks require a motorDriver that can set brake!
|
// DC tracks require a motorDriver that can set brake!
|
||||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
if (mode & TRACK_MODE_DC) {
|
||||||
#if defined(ARDUINO_AVR_UNO)
|
#if defined(ARDUINO_AVR_UNO)
|
||||||
DIAG(F("Uno has no PWM timers available for DC"));
|
DIAG(F("Uno has no PWM timers available for DC"));
|
||||||
return false;
|
return false;
|
||||||
|
@ -209,21 +211,37 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
pinpair p = track[trackToSet]->getSignalPin();
|
pinpair p = track[trackToSet]->getSignalPin();
|
||||||
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
|
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
|
||||||
gpio_reset_pin((gpio_num_t)p.pin);
|
gpio_reset_pin((gpio_num_t)p.pin);
|
||||||
pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input
|
|
||||||
if (p.invpin != UNUSED_PIN) {
|
if (p.invpin != UNUSED_PIN) {
|
||||||
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
|
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
|
||||||
gpio_reset_pin((gpio_num_t)p.invpin);
|
gpio_reset_pin((gpio_num_t)p.invpin);
|
||||||
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
|
|
||||||
}
|
}
|
||||||
|
#ifdef BOOSTER_INPUT
|
||||||
|
if (mode & TRACK_MODE_BOOST) {
|
||||||
|
//DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin);
|
||||||
|
pinMode(BOOSTER_INPUT, INPUT);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
} else // elseif clause continues
|
||||||
|
#endif
|
||||||
|
if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) {
|
||||||
|
// gpio_reset_pin may reset to input
|
||||||
|
pinMode(p.pin, OUTPUT);
|
||||||
|
if (p.invpin != UNUSED_PIN)
|
||||||
|
pinMode(p.invpin, OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
if (mode==TRACK_MODE_PROG) {
|
if (mode & TRACK_MODE_PROG) {
|
||||||
#else
|
#else
|
||||||
if (false) {
|
if (false) {
|
||||||
#endif
|
#endif
|
||||||
// only allow 1 track to be prog
|
// only allow 1 track to be prog
|
||||||
FOR_EACH_TRACK(t)
|
FOR_EACH_TRACK(t)
|
||||||
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
|
if ( (track[t]->getMode() & TRACK_MODE_PROG) && t != trackToSet) {
|
||||||
track[t]->setPower(POWERMODE::OFF);
|
track[t]->setPower(POWERMODE::OFF);
|
||||||
track[t]->setMode(TRACK_MODE_NONE);
|
track[t]->setMode(TRACK_MODE_NONE);
|
||||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||||
|
@ -233,24 +251,56 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
} else {
|
} else {
|
||||||
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
|
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
|
||||||
}
|
}
|
||||||
track[trackToSet]->setMode(mode);
|
|
||||||
trackDCAddr[trackToSet]=dcAddr;
|
|
||||||
streamTrackState(NULL,trackToSet);
|
|
||||||
|
|
||||||
// When a track is switched, we must clear any side effects of its previous
|
// When a track is switched, we must clear any side effects of its previous
|
||||||
// state, otherwise trains run away or just dont move.
|
// state, otherwise trains run away or just dont move.
|
||||||
|
|
||||||
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
||||||
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
|
if (mode & TRACK_MODE_DC) {
|
||||||
|
if (trackDCAddr[trackToSet] != dcAddr) {
|
||||||
|
// new or changed DC Addr, run the new setup
|
||||||
|
if (trackDCAddr[trackToSet] != 0) {
|
||||||
|
// if we change dcAddr and not only
|
||||||
|
// change from another mode,
|
||||||
|
// first detach old DC signal
|
||||||
|
track[trackToSet]->detachDCSignal();
|
||||||
|
}
|
||||||
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
int trackfound = -1;
|
||||||
|
FOR_EACH_TRACK(t) {
|
||||||
|
//DIAG(F("Checking track %c mode %x dcAddr %d"), 'A'+t, track[t]->getMode(), trackDCAddr[t]);
|
||||||
|
if (t != trackToSet // not our track
|
||||||
|
&& (track[t]->getMode() & TRACK_MODE_DC) // right mode
|
||||||
|
&& trackDCAddr[t] == dcAddr) { // right addr
|
||||||
|
//DIAG(F("Found track %c"), 'A'+t);
|
||||||
|
trackfound = t;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (trackfound > -1) {
|
||||||
|
DCCTimer::DCCEXanalogCopyChannel(track[trackfound]->getBrakePinSigned(),
|
||||||
|
track[trackToSet]->getBrakePinSigned());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
// set future DC Addr;
|
||||||
|
trackDCAddr[trackToSet]=dcAddr;
|
||||||
|
} else {
|
||||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||||
track[trackToSet]->detachDCSignal();
|
track[trackToSet]->detachDCSignal();
|
||||||
track[trackToSet]->setBrake(false);
|
track[trackToSet]->setBrake(false);
|
||||||
|
trackDCAddr[trackToSet]=0; // clear that an addr is set for DC as this is not a DC track
|
||||||
}
|
}
|
||||||
|
track[trackToSet]->setMode(mode);
|
||||||
|
|
||||||
|
// BOOST:
|
||||||
|
// Leave it as is
|
||||||
|
// otherwise:
|
||||||
// EXT is a special case where the signal pin is
|
// EXT is a special case where the signal pin is
|
||||||
// turned off. So unless that is set, the signal
|
// turned off. So unless that is set, the signal
|
||||||
// pin should be turned on
|
// pin should be turned on
|
||||||
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
|
if (!(mode & TRACK_MODE_BOOST))
|
||||||
|
track[trackToSet]->enableSignal(!(mode & TRACK_MODE_EXT));
|
||||||
|
|
||||||
#ifndef ARDUINO_ARCH_ESP32
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
// re-evaluate HighAccuracy mode
|
// re-evaluate HighAccuracy mode
|
||||||
|
@ -260,7 +310,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
// DC tracks must not have the DCC PWM switched on
|
// DC tracks must not have the DCC PWM switched on
|
||||||
// so we globally turn it off if one of the PWM
|
// so we globally turn it off if one of the PWM
|
||||||
// capable tracks is now DC or DCX.
|
// capable tracks is now DC or DCX.
|
||||||
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
|
if (track[t]->getMode() & TRACK_MODE_DC) {
|
||||||
if (track[t]->isPWMCapable()) {
|
if (track[t]->isPWMCapable()) {
|
||||||
canDo=false; // this track is capable but can not run PWM
|
canDo=false; // this track is capable but can not run PWM
|
||||||
break; // in this mode, so abort and prevent globally below
|
break; // in this mode, so abort and prevent globally below
|
||||||
|
@ -268,7 +318,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
track[t]->trackPWM=false; // this track sure can not run with PWM
|
track[t]->trackPWM=false; // this track sure can not run with PWM
|
||||||
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
||||||
}
|
}
|
||||||
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
|
} else if (track[t]->getMode() & (TRACK_MODE_MAIN |TRACK_MODE_PROG)) {
|
||||||
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
||||||
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
||||||
canDo &= track[t]->trackPWM;
|
canDo &= track[t]->trackPWM;
|
||||||
|
@ -286,98 +336,135 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||||
#else
|
#else
|
||||||
// For ESP32 we just reinitialize the DCC Waveform
|
// For ESP32 we just reinitialize the DCC Waveform
|
||||||
DCCWaveform::begin();
|
DCCWaveform::begin();
|
||||||
|
// setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal
|
||||||
|
track[trackToSet]->setMode(mode);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// This block must be AFTER the PWM-Timer modifications
|
// This block must be AFTER the PWM-Timer modifications
|
||||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
if (mode & TRACK_MODE_DC) {
|
||||||
// DC tracks need to be given speed of the throttle for that cab address
|
// DC tracks need to be given speed of the throttle for that cab address
|
||||||
// otherwise will not match other tracks on same cab.
|
// otherwise will not match other tracks on same cab.
|
||||||
// This also needs to allow for inverted DCX
|
// This also needs to allow for inverted DCX
|
||||||
applyDCSpeed(trackToSet);
|
applyDCSpeed(trackToSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal running tracks are set to the global power state
|
// Turn off power if we changed the mode of this track
|
||||||
track[trackToSet]->setPower(
|
if (mode != oldmode)
|
||||||
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
|
track[trackToSet]->setPower(POWERMODE::OFF);
|
||||||
mainPowerGuess : POWERMODE::OFF);
|
streamTrackState(NULL,trackToSet);
|
||||||
|
|
||||||
//DIAG(F("TrackMode=%d"),mode);
|
//DIAG(F("TrackMode=%d"),mode);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackManager::applyDCSpeed(byte t) {
|
void TrackManager::applyDCSpeed(byte t) {
|
||||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
track[t]->setDCSignal(DCC::getThrottleSpeedByte(trackDCAddr[t]),
|
||||||
if (track[t]->getMode()==TRACK_MODE_DCX)
|
DCC::getThrottleFrequency(trackDCAddr[t]));
|
||||||
speedByte = speedByte ^ 128; // reverse direction bit
|
|
||||||
track[t]->setDCSignal(speedByte);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||||
{
|
{
|
||||||
|
|
||||||
if (params==0) { // <=> List track assignments
|
if (params==0) { // <=> List track assignments
|
||||||
FOR_EACH_TRACK(t)
|
FOR_EACH_TRACK(t)
|
||||||
streamTrackState(stream,t);
|
streamTrackState(stream,t);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
p[0]-="A"_hk; // convert A... to 0....
|
||||||
|
|
||||||
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
|
if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN>
|
||||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||||
|
|
||||||
#ifndef DISABLE_PROG
|
#ifndef DISABLE_PROG
|
||||||
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
|
if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
|
||||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE>
|
if (params==2 && (p[1]=="OFF"_hk || p[1]=="NONE"_hk)) // <= id OFF> <= id NONE>
|
||||||
return setTrackMode(p[0],TRACK_MODE_NONE);
|
return setTrackMode(p[0],TRACK_MODE_NONE);
|
||||||
|
|
||||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
if (params==2 && p[1]=="EXT"_hk) // <= id EXT>
|
||||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||||
|
#ifdef BOOSTER_INPUT
|
||||||
|
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>
|
||||||
|
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
|
||||||
|
|
||||||
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
|
if (params==2 && p[1]=="INV"_hk) // <= id INV>
|
||||||
|
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV);
|
||||||
|
|
||||||
|
if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab>
|
||||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||||
|
|
||||||
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
|
if (params==3 && p[1]=="DCX"_hk && p[2]>0) // <= id DCX cab>
|
||||||
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
|
return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackManager::streamTrackState(Print* stream, byte t) {
|
const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
||||||
// null stream means send to commandDistributor for broadcast
|
const FSH *modename=F("---");
|
||||||
if (track[t]==NULL) return;
|
|
||||||
auto format=F("");
|
if (tm & TRACK_MODE_MAIN) {
|
||||||
switch(track[t]->getMode()) {
|
if(tm & TRACK_MODE_AUTOINV)
|
||||||
case TRACK_MODE_MAIN:
|
modename=F("MAIN A");
|
||||||
format=F("<= %c MAIN>\n");
|
else if (tm & TRACK_MODE_INV)
|
||||||
break;
|
modename=F("MAIN I>\n");
|
||||||
#ifndef DISABLE_PROG
|
else
|
||||||
case TRACK_MODE_PROG:
|
modename=F("MAIN");
|
||||||
format=F("<= %c PROG>\n");
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
case TRACK_MODE_NONE:
|
|
||||||
format=F("<= %c NONE>\n");
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_EXT:
|
|
||||||
format=F("<= %c EXT>\n");
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_DC:
|
|
||||||
format=F("<= %c DC %d>\n");
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_DCX:
|
|
||||||
format=F("<= %c DCX %d>\n");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break; // unknown, dont care
|
|
||||||
}
|
}
|
||||||
if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]);
|
#ifndef DISABLE_PROG
|
||||||
else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]);
|
else if (tm & TRACK_MODE_PROG)
|
||||||
|
modename=F("PROG");
|
||||||
|
#endif
|
||||||
|
else if (tm & TRACK_MODE_NONE)
|
||||||
|
modename=F("NONE");
|
||||||
|
else if(tm & TRACK_MODE_EXT)
|
||||||
|
modename=F("EXT");
|
||||||
|
else if(tm & TRACK_MODE_BOOST) {
|
||||||
|
if(tm & TRACK_MODE_AUTOINV)
|
||||||
|
modename=F("BOOST A");
|
||||||
|
else if (tm & TRACK_MODE_INV)
|
||||||
|
modename=F("BOOST I");
|
||||||
|
else
|
||||||
|
modename=F("BOOST");
|
||||||
|
}
|
||||||
|
else if (tm & TRACK_MODE_DC) {
|
||||||
|
if (tm & TRACK_MODE_INV)
|
||||||
|
modename=F("DCX");
|
||||||
|
else
|
||||||
|
modename=F("DC");
|
||||||
|
}
|
||||||
|
return modename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// null stream means send to commandDistributor for broadcast
|
||||||
|
void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||||
|
const FSH *format;
|
||||||
|
|
||||||
|
if (track[t]==NULL) return;
|
||||||
|
TRACK_MODE tm = track[t]->getMode();
|
||||||
|
if (tm & TRACK_MODE_DC)
|
||||||
|
format=F("<= %c %S %d>\n");
|
||||||
|
else
|
||||||
|
format=F("<= %c %S>\n");
|
||||||
|
|
||||||
|
const FSH *modename=getModeName(tm);
|
||||||
|
if (stream) { // null stream means send to commandDistributor for broadcast
|
||||||
|
StringFormatter::send(stream,format,'A'+t, modename, trackDCAddr[t]);
|
||||||
|
} else {
|
||||||
|
CommandDistributor::broadcastTrackState(format,'A'+t, modename, trackDCAddr[t]);
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||||
|
@ -392,13 +479,13 @@ void TrackManager::loop() {
|
||||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||||
if (track[nextCycleTrack]==NULL) return;
|
if (track[nextCycleTrack]==NULL) return;
|
||||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||||
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
|
bool useProgLimit=dontLimitProg ? false : (bool)(track[nextCycleTrack]->getMode() & TRACK_MODE_PROG);
|
||||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||||
}
|
}
|
||||||
|
|
||||||
MotorDriver * TrackManager::getProgDriver() {
|
MotorDriver * TrackManager::getProgDriver() {
|
||||||
FOR_EACH_TRACK(t)
|
FOR_EACH_TRACK(t)
|
||||||
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
|
if (track[t]->getMode() & TRACK_MODE_PROG) return track[t];
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -406,64 +493,113 @@ MotorDriver * TrackManager::getProgDriver() {
|
||||||
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||||
std::vector<MotorDriver *> v;
|
std::vector<MotorDriver *> v;
|
||||||
FOR_EACH_TRACK(t)
|
FOR_EACH_TRACK(t)
|
||||||
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
|
if (track[t]->getMode() & TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
// Set track power for all tracks with this mode
|
||||||
if (!setProg) mainPowerGuess=mode;
|
void TrackManager::setTrackPower(TRACK_MODE trackmodeToMatch, POWERMODE powermode) {
|
||||||
|
bool didChange=false;
|
||||||
FOR_EACH_TRACK(t) {
|
FOR_EACH_TRACK(t) {
|
||||||
MotorDriver *driver=track[t];
|
MotorDriver *driver=track[t];
|
||||||
if (!driver) continue;
|
TRACK_MODE trackmodeOfTrack = driver->getMode();
|
||||||
switch (track[t]->getMode()) {
|
if (trackmodeToMatch & trackmodeOfTrack) {
|
||||||
case TRACK_MODE_MAIN:
|
if (powermode != driver->getPower())
|
||||||
if (setProg) break;
|
didChange=true;
|
||||||
// toggle brake before turning power on - resets overcurrent error
|
if (powermode == POWERMODE::ON) {
|
||||||
// on the Pololu board if brake is wired to ^D2.
|
if (trackmodeOfTrack & TRACK_MODE_DC) {
|
||||||
// XXX see if we can make this conditional
|
|
||||||
driver->setBrake(true);
|
|
||||||
driver->setBrake(false); // DCC runs with brake off
|
|
||||||
driver->setPower(mode);
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_DC:
|
|
||||||
case TRACK_MODE_DCX:
|
|
||||||
if (setProg) break;
|
|
||||||
driver->setBrake(true); // DC starts with brake on
|
driver->setBrake(true); // DC starts with brake on
|
||||||
applyDCSpeed(t); // speed match DCC throttles
|
applyDCSpeed(t); // speed match DCC throttles
|
||||||
driver->setPower(mode);
|
} else {
|
||||||
break;
|
// toggle brake before turning power on - resets overcurrent error
|
||||||
case TRACK_MODE_PROG:
|
// on the Pololu board if brake is wired to ^D2.
|
||||||
if (!setProg) break;
|
|
||||||
driver->setBrake(true);
|
driver->setBrake(true);
|
||||||
driver->setBrake(false);
|
driver->setBrake(false); // DCC runs with brake off
|
||||||
driver->setPower(mode);
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_EXT:
|
|
||||||
driver->setBrake(true);
|
|
||||||
driver->setBrake(false);
|
|
||||||
driver->setPower(mode);
|
|
||||||
break;
|
|
||||||
case TRACK_MODE_NONE:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
driver->setPower(powermode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (didChange)
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set track power for this track, inependent of mode
|
||||||
|
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
|
||||||
|
MotorDriver *driver=track[t];
|
||||||
|
if (driver == NULL) { // track is not defined at all
|
||||||
|
DIAG(F("Error: Track %c does not exist"), t+'A');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TRACK_MODE trackmode = driver->getMode();
|
||||||
|
POWERMODE oldpower = driver->getPower();
|
||||||
|
if (trackmode & TRACK_MODE_NONE) {
|
||||||
|
driver->setBrake(true); // Track is unused. Brake is good to have.
|
||||||
|
powermode = POWERMODE::OFF; // Track is unused. Force it to OFF
|
||||||
|
} else if (trackmode & TRACK_MODE_DC) { // includes inverted DC (called DCX)
|
||||||
|
if (powermode == POWERMODE::ON) {
|
||||||
|
driver->setBrake(true); // DC starts with brake on
|
||||||
|
applyDCSpeed(t); // speed match DCC throttles
|
||||||
|
}
|
||||||
|
} else /* MAIN PROG EXT BOOST */ {
|
||||||
|
if (powermode == POWERMODE::ON) {
|
||||||
|
// toggle brake before turning power on - resets overcurrent error
|
||||||
|
// on the Pololu board if brake is wired to ^D2.
|
||||||
|
driver->setBrake(true);
|
||||||
|
driver->setBrake(false); // DCC runs with brake off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
driver->setPower(powermode);
|
||||||
|
if (oldpower != driver->getPower())
|
||||||
|
CommandDistributor::broadcastPower();
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns state of the one and only prog track
|
||||||
POWERMODE TrackManager::getProgPower() {
|
POWERMODE TrackManager::getProgPower() {
|
||||||
FOR_EACH_TRACK(t)
|
FOR_EACH_TRACK(t)
|
||||||
if (track[t]->getMode()==TRACK_MODE_PROG)
|
if (track[t]->getMode() & TRACK_MODE_PROG)
|
||||||
return track[t]->getPower();
|
return track[t]->getPower(); // optimize: there is max one prog track
|
||||||
return POWERMODE::OFF;
|
return POWERMODE::OFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns on if all are on. returns off otherwise
|
||||||
|
POWERMODE TrackManager::getMainPower() {
|
||||||
|
POWERMODE result = POWERMODE::OFF;
|
||||||
|
FOR_EACH_TRACK(t) {
|
||||||
|
if (track[t]->getMode() & TRACK_MODE_MAIN) {
|
||||||
|
POWERMODE p = track[t]->getPower();
|
||||||
|
if (p == POWERMODE::OFF)
|
||||||
|
return POWERMODE::OFF; // done and out
|
||||||
|
if (p == POWERMODE::ON)
|
||||||
|
result = POWERMODE::ON;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrackManager::getPower(byte t, char s[]) {
|
||||||
|
if (t > lastTrack)
|
||||||
|
return false;
|
||||||
|
if (track[t]) {
|
||||||
|
s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0';
|
||||||
|
s[2] = t + 'A';
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
||||||
// This function is for backward JMRI compatibility only
|
// This function is for backward JMRI compatibility only
|
||||||
// It reports the first track only, as main, regardless of track settings.
|
// It reports the first track only, as main, regardless of track settings.
|
||||||
// <c MeterName value C/V unit min max res warn>
|
// <c MeterName value C/V unit min max res warn>
|
||||||
|
#ifdef HAS_ENOUGH_MEMORY
|
||||||
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
||||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
||||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||||
|
#else
|
||||||
|
(void)stream;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackManager::reportCurrent(Print* stream) {
|
void TrackManager::reportCurrent(Print* stream) {
|
||||||
|
@ -497,7 +633,7 @@ void TrackManager::setJoin(bool joined) {
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
if (joined) {
|
if (joined) {
|
||||||
FOR_EACH_TRACK(t) {
|
FOR_EACH_TRACK(t) {
|
||||||
if (track[t]->getMode()==TRACK_MODE_PROG) {
|
if (track[t]->getMode() & TRACK_MODE_PROG) {
|
||||||
tempProgTrack = t;
|
tempProgTrack = t;
|
||||||
setTrackMode(t, TRACK_MODE_MAIN);
|
setTrackMode(t, TRACK_MODE_MAIN);
|
||||||
break;
|
break;
|
||||||
|
@ -518,3 +654,24 @@ void TrackManager::setJoin(bool joined) {
|
||||||
progTrackSyncMain=joined;
|
progTrackSyncMain=joined;
|
||||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
|
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool TrackManager::isPowerOn(byte t) {
|
||||||
|
if (track[t]->getPower()!=POWERMODE::ON)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TrackManager::isProg(byte t) {
|
||||||
|
if (track[t]->getMode() & TRACK_MODE_PROG)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TRACK_MODE TrackManager::getMode(byte t) {
|
||||||
|
return (track[t]->getMode());
|
||||||
|
}
|
||||||
|
|
||||||
|
int16_t TrackManager::returnDCAddr(byte t) {
|
||||||
|
return (trackDCAddr[t]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* © 2022 Chris Harlow
|
* © 2022 Chris Harlow
|
||||||
* © 2022 Harald Barth
|
* © 2022-2024 Harald Barth
|
||||||
|
* © 2023 Colin Murdoch
|
||||||
|
*
|
||||||
* All rights reserved.
|
* All rights reserved.
|
||||||
*
|
*
|
||||||
* This file is part of CommandStation-EX
|
* This file is part of CommandStation-EX
|
||||||
|
@ -37,10 +39,14 @@ const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
|
||||||
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
|
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
|
||||||
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
|
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
|
||||||
|
|
||||||
|
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||||
|
const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||||
|
const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||||
|
|
||||||
class TrackManager {
|
class TrackManager {
|
||||||
public:
|
public:
|
||||||
static void Setup(const FSH * shieldName,
|
static void Setup(const FSH * shieldName,
|
||||||
MotorDriver * track0,
|
MotorDriver * track0=NULL,
|
||||||
MotorDriver * track1=NULL,
|
MotorDriver * track1=NULL,
|
||||||
MotorDriver * track2=NULL,
|
MotorDriver * track2=NULL,
|
||||||
MotorDriver * track3=NULL,
|
MotorDriver * track3=NULL,
|
||||||
|
@ -51,32 +57,43 @@ class TrackManager {
|
||||||
);
|
);
|
||||||
|
|
||||||
static void setDCCSignal( bool on);
|
static void setDCCSignal( bool on);
|
||||||
static void setCutout( bool on);
|
|
||||||
static void setPROGSignal( bool on);
|
static void setPROGSignal( bool on);
|
||||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||||
static MotorDriver * getProgDriver();
|
static MotorDriver * getProgDriver();
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
static std::vector<MotorDriver *>getMainDrivers();
|
static std::vector<MotorDriver *>getMainDrivers();
|
||||||
#endif
|
#endif
|
||||||
static void setPower2(bool progTrack,POWERMODE mode);
|
|
||||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||||
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
|
static void setTrackPower(POWERMODE mode, byte t);
|
||||||
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
|
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
|
||||||
|
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
|
||||||
|
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
|
||||||
|
|
||||||
static const int16_t MAX_TRACKS=8;
|
static const int16_t MAX_TRACKS=8;
|
||||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||||
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
|
static bool parseEqualSign(Print * stream, int16_t params, int16_t p[]);
|
||||||
static void loop();
|
static void loop();
|
||||||
static POWERMODE getMainPower() {return mainPowerGuess;}
|
static POWERMODE getMainPower();
|
||||||
static POWERMODE getProgPower();
|
static POWERMODE getProgPower();
|
||||||
|
static inline POWERMODE getPower(byte t) { return track[t]->getPower(); }
|
||||||
|
static bool getPower(byte t, char s[]);
|
||||||
static void setJoin(bool join);
|
static void setJoin(bool join);
|
||||||
static bool isJoined() { return progTrackSyncMain;}
|
static bool isJoined() { return progTrackSyncMain;}
|
||||||
|
static inline bool isActive (byte tr) {
|
||||||
|
if (tr > lastTrack) return false;
|
||||||
|
return track[tr]->getMode() & (TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT);}
|
||||||
static void setJoinRelayPin(byte joinRelayPin);
|
static void setJoinRelayPin(byte joinRelayPin);
|
||||||
static void sampleCurrent();
|
static void sampleCurrent();
|
||||||
static void reportGauges(Print* stream);
|
static void reportGauges(Print* stream);
|
||||||
static void reportCurrent(Print* stream);
|
static void reportCurrent(Print* stream);
|
||||||
static void reportObsoleteCurrent(Print* stream);
|
static void reportObsoleteCurrent(Print* stream);
|
||||||
static void streamTrackState(Print* stream, byte t);
|
static void streamTrackState(Print* stream, byte t);
|
||||||
|
static bool isPowerOn(byte t);
|
||||||
|
static bool isProg(byte t);
|
||||||
|
static TRACK_MODE getMode(byte t);
|
||||||
|
static int16_t returnDCAddr(byte t);
|
||||||
|
static const FSH* getModeName(TRACK_MODE Mode);
|
||||||
|
|
||||||
static int16_t joinRelay;
|
static int16_t joinRelay;
|
||||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||||
|
@ -91,12 +108,11 @@ class TrackManager {
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void addTrack(byte t, MotorDriver* driver);
|
static void addTrack(byte t, MotorDriver* driver);
|
||||||
static byte lastTrack;
|
static int8_t lastTrack;
|
||||||
static byte nextCycleTrack;
|
static byte nextCycleTrack;
|
||||||
static POWERMODE mainPowerGuess;
|
|
||||||
static void applyDCSpeed(byte t);
|
static void applyDCSpeed(byte t);
|
||||||
|
|
||||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
|
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
static byte tempProgTrack; // holds the prog track number during join
|
static byte tempProgTrack; // holds the prog track number during join
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -123,7 +123,6 @@
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define DIAG_IO
|
|
||||||
// Static setClosed function is invoked from close(), throw() etc. to perform the
|
// Static setClosed function is invoked from close(), throw() etc. to perform the
|
||||||
// common parts of the turnout operation. Code which is specific to a turnout
|
// common parts of the turnout operation. Code which is specific to a turnout
|
||||||
// type should be placed in the virtual function setClosedInternal(bool) which is
|
// type should be placed in the virtual function setClosedInternal(bool) which is
|
||||||
|
|
269
Turntables.cpp
Normal file
269
Turntables.cpp
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
/*
|
||||||
|
* © 2023 Peter Cole
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "Turntables.h"
|
||||||
|
#include "StringFormatter.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
#include "EXRAIL2.h"
|
||||||
|
#include "DCC.h"
|
||||||
|
|
||||||
|
// No turntable support without HAL
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Protected static data
|
||||||
|
*/
|
||||||
|
Turntable *Turntable::_firstTurntable = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public static data
|
||||||
|
*/
|
||||||
|
int Turntable::turntablelistHash = 0;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Protected static functions
|
||||||
|
*/
|
||||||
|
// Add new turntable to end of list
|
||||||
|
|
||||||
|
void Turntable::add(Turntable *tto) {
|
||||||
|
if (!_firstTurntable) {
|
||||||
|
_firstTurntable = tto;
|
||||||
|
} else {
|
||||||
|
Turntable *ptr = _firstTurntable;
|
||||||
|
for ( ; ptr->_nextTurntable!=0; ptr=ptr->_nextTurntable) {}
|
||||||
|
ptr->_nextTurntable = tto;
|
||||||
|
}
|
||||||
|
turntablelistHash++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a position
|
||||||
|
void Turntable::addPosition(uint8_t idx, uint16_t value, uint16_t angle) {
|
||||||
|
_turntablePositions.insert(idx, value, angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get value for position
|
||||||
|
uint16_t Turntable::getPositionValue(uint8_t position) {
|
||||||
|
TurntablePosition* currentPosition = _turntablePositions.getHead();
|
||||||
|
while (currentPosition) {
|
||||||
|
if (currentPosition->index == position) {
|
||||||
|
return currentPosition->data;
|
||||||
|
}
|
||||||
|
currentPosition = currentPosition->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get value for position
|
||||||
|
uint16_t Turntable::getPositionAngle(uint8_t position) {
|
||||||
|
TurntablePosition* currentPosition = _turntablePositions.getHead();
|
||||||
|
while (currentPosition) {
|
||||||
|
if (currentPosition->index == position) {
|
||||||
|
return currentPosition->angle;
|
||||||
|
}
|
||||||
|
currentPosition = currentPosition->next;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the count of positions associated with the turntable
|
||||||
|
uint8_t Turntable::getPositionCount() {
|
||||||
|
TurntablePosition* currentPosition = _turntablePositions.getHead();
|
||||||
|
uint8_t count = 0;
|
||||||
|
while (currentPosition) {
|
||||||
|
count++;
|
||||||
|
currentPosition = currentPosition->next;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public static functions
|
||||||
|
*/
|
||||||
|
// Find turntable from list
|
||||||
|
Turntable *Turntable::get(uint16_t id) {
|
||||||
|
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable)
|
||||||
|
if (tto->_turntableData.id == id) return tto;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find turntable via Vpin
|
||||||
|
Turntable *Turntable::getByVpin(VPIN vpin) {
|
||||||
|
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable) {
|
||||||
|
if (tto->isEXTT()) {
|
||||||
|
EXTTTurntable *exttTto = static_cast<EXTTTurntable*>(tto);
|
||||||
|
if (exttTto->getVpin() == vpin) {
|
||||||
|
return tto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current position for turntable with the specified ID
|
||||||
|
uint8_t Turntable::getPosition(uint16_t id) {
|
||||||
|
Turntable *tto = get(id);
|
||||||
|
if (!tto) return false;
|
||||||
|
return tto->getPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Got the moving state of the specified turntable
|
||||||
|
bool Turntable::ttMoving(uint16_t id) {
|
||||||
|
Turntable *tto = get(id);
|
||||||
|
if (!tto) return false;
|
||||||
|
return tto->isMoving();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initiate a turntable move
|
||||||
|
bool Turntable::setPosition(uint16_t id, uint8_t position, uint8_t activity) {
|
||||||
|
#if defined(DIAG_IO)
|
||||||
|
DIAG(F("Rotate turntable %d to position %d, activity %d)"), id, position, activity);
|
||||||
|
#endif
|
||||||
|
Turntable *tto = Turntable::get(id);
|
||||||
|
if (!tto) return false;
|
||||||
|
if (tto->isMoving()) return false;
|
||||||
|
bool ok = tto->setPositionInternal(position, activity);
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
// We only deal with broadcasts for DCC turntables here, EXTT in the device driver
|
||||||
|
if (!tto->isEXTT()) {
|
||||||
|
CommandDistributor::broadcastTurntable(id, position, false);
|
||||||
|
}
|
||||||
|
// Trigger EXRAIL rotateEvent for both types here if changed
|
||||||
|
#if defined(EXRAIL_ACTIVE)
|
||||||
|
bool rotated = false;
|
||||||
|
if (position != tto->_previousPosition) rotated = true;
|
||||||
|
RMFT2::rotateEvent(id, rotated);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* EXTTTurntable - EX-Turntable device.
|
||||||
|
*
|
||||||
|
*************************************************************************************/
|
||||||
|
// Private constructor
|
||||||
|
EXTTTurntable::EXTTTurntable(uint16_t id, VPIN vpin) :
|
||||||
|
Turntable(id, TURNTABLE_EXTT)
|
||||||
|
{
|
||||||
|
_exttTurntableData.vpin = vpin;
|
||||||
|
}
|
||||||
|
|
||||||
|
using DevState = IODevice::DeviceStateEnum;
|
||||||
|
|
||||||
|
// Create function
|
||||||
|
Turntable *EXTTTurntable::create(uint16_t id, VPIN vpin) {
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
Turntable *tto = get(id);
|
||||||
|
if (tto) {
|
||||||
|
if (tto->isType(TURNTABLE_EXTT)) {
|
||||||
|
EXTTTurntable *extt = (EXTTTurntable *)tto;
|
||||||
|
extt->_exttTurntableData.vpin = vpin;
|
||||||
|
return tto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!IODevice::exists(vpin)) return nullptr;
|
||||||
|
if (IODevice::getStatus(vpin) == DevState::DEVSTATE_FAILED) return nullptr;
|
||||||
|
if (Turntable::getByVpin(vpin)) return nullptr;
|
||||||
|
tto = (Turntable *)new EXTTTurntable(id, vpin);
|
||||||
|
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
|
||||||
|
return tto;
|
||||||
|
#else
|
||||||
|
(void)id;
|
||||||
|
(void)vpin;
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void EXTTTurntable::print(Print *stream) {
|
||||||
|
StringFormatter::send(stream, F("<i %d EXTURNTABLE %d>\n"), _turntableData.id, _exttTurntableData.vpin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// EX-Turntable specific code for moving to the specified position
|
||||||
|
bool EXTTTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
int16_t value;
|
||||||
|
if (position == 0) {
|
||||||
|
value = 0; // Position 0 is just to send activities
|
||||||
|
} else {
|
||||||
|
if (activity > 1) return false; // If sending a position update, only phase changes valid (0|1)
|
||||||
|
value = getPositionValue(position); // Get position value from position list
|
||||||
|
}
|
||||||
|
if (position > 0 && !value) return false; // Return false if it's not a valid position
|
||||||
|
// Set position via device driver
|
||||||
|
_previousPosition = _turntableData.position;
|
||||||
|
_turntableData.position = position;
|
||||||
|
EXTurntable::writeAnalogue(_exttTurntableData.vpin, value, activity);
|
||||||
|
#else
|
||||||
|
(void)position;
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* DCCTurntable - DCC Turntable device.
|
||||||
|
*
|
||||||
|
*************************************************************************************/
|
||||||
|
// Private constructor
|
||||||
|
DCCTurntable::DCCTurntable(uint16_t id) : Turntable(id, TURNTABLE_DCC) {}
|
||||||
|
|
||||||
|
// Create function
|
||||||
|
Turntable *DCCTurntable::create(uint16_t id) {
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
Turntable *tto = get(id);
|
||||||
|
if (!tto) {
|
||||||
|
tto = (Turntable *)new DCCTurntable(id);
|
||||||
|
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
|
||||||
|
}
|
||||||
|
return tto;
|
||||||
|
#else
|
||||||
|
(void)id;
|
||||||
|
return NULL;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void DCCTurntable::print(Print *stream) {
|
||||||
|
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) {
|
||||||
|
(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);
|
||||||
|
#else
|
||||||
|
(void)position;
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
243
Turntables.h
Normal file
243
Turntables.h
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
/*
|
||||||
|
* © 2023 Peter Cole
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TURNTABLES_H
|
||||||
|
#define TURNTABLES_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "IODevice.h"
|
||||||
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
|
// No turntable support without HAL
|
||||||
|
#ifndef IO_NO_HAL
|
||||||
|
|
||||||
|
// Turntable type definitions
|
||||||
|
// EXTT = EX-Turntable
|
||||||
|
// DCC = DCC accessory turntables - to be added later
|
||||||
|
enum {
|
||||||
|
TURNTABLE_EXTT = 0,
|
||||||
|
TURNTABLE_DCC = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* Turntable positions.
|
||||||
|
*
|
||||||
|
*************************************************************************************/
|
||||||
|
struct TurntablePosition {
|
||||||
|
uint8_t index;
|
||||||
|
uint16_t data;
|
||||||
|
uint16_t angle;
|
||||||
|
TurntablePosition* next;
|
||||||
|
|
||||||
|
TurntablePosition(uint8_t idx, uint16_t value, uint16_t angle) : index(idx), data(value), angle(angle), next(nullptr) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class TurntablePositionList {
|
||||||
|
public:
|
||||||
|
TurntablePositionList() : head(nullptr) {}
|
||||||
|
|
||||||
|
void insert(uint8_t idx, uint16_t value, uint16_t angle) {
|
||||||
|
TurntablePosition* newPosition = new TurntablePosition(idx, value, angle);
|
||||||
|
if(!head) {
|
||||||
|
head = newPosition;
|
||||||
|
} else {
|
||||||
|
newPosition->next = head;
|
||||||
|
head = newPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TurntablePosition* getHead() {
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TurntablePosition* head;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* Turntable - Base class for turntables.
|
||||||
|
*
|
||||||
|
*************************************************************************************/
|
||||||
|
|
||||||
|
class Turntable {
|
||||||
|
protected:
|
||||||
|
/*
|
||||||
|
* Object data
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Data common to all turntable types
|
||||||
|
struct TurntableData {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
bool hidden : 1;
|
||||||
|
bool turntableType : 1;
|
||||||
|
uint8_t position : 6; // Allows up to 63 positions including 0/home
|
||||||
|
};
|
||||||
|
uint8_t flags;
|
||||||
|
};
|
||||||
|
uint16_t id;
|
||||||
|
} _turntableData;
|
||||||
|
|
||||||
|
// Pointer to next turntable object
|
||||||
|
Turntable *_nextTurntable = 0;
|
||||||
|
|
||||||
|
// Linked list for positions
|
||||||
|
TurntablePositionList _turntablePositions;
|
||||||
|
|
||||||
|
// Store the previous position to allow checking for changes
|
||||||
|
uint8_t _previousPosition = 0;
|
||||||
|
|
||||||
|
// Store the current state of the turntable
|
||||||
|
bool _isMoving = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
Turntable(uint16_t id, uint8_t turntableType) {
|
||||||
|
_turntableData.id = id;
|
||||||
|
_turntableData.turntableType = turntableType;
|
||||||
|
_turntableData.hidden = false;
|
||||||
|
_turntableData.position = 0;
|
||||||
|
add(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static data
|
||||||
|
*/
|
||||||
|
static Turntable *_firstTurntable;
|
||||||
|
static int _turntablelistHash;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Virtual functions
|
||||||
|
*/
|
||||||
|
virtual bool setPositionInternal(uint8_t position, uint8_t activity) = 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static functions
|
||||||
|
*/
|
||||||
|
static void add(Turntable *tto);
|
||||||
|
|
||||||
|
public:
|
||||||
|
static Turntable *get(uint16_t id);
|
||||||
|
static Turntable *getByVpin(VPIN vpin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Static data
|
||||||
|
*/
|
||||||
|
static int turntablelistHash;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public base class functions
|
||||||
|
*/
|
||||||
|
inline uint8_t getPosition() { return _turntableData.position; }
|
||||||
|
inline bool isHidden() { return _turntableData.hidden; }
|
||||||
|
inline void setHidden(bool h) {_turntableData.hidden=h; }
|
||||||
|
inline bool isType(uint8_t type) { return _turntableData.turntableType == type; }
|
||||||
|
inline bool isEXTT() const { return _turntableData.turntableType == TURNTABLE_EXTT; }
|
||||||
|
inline uint16_t getId() { return _turntableData.id; }
|
||||||
|
inline Turntable *next() { return _nextTurntable; }
|
||||||
|
void printState(Print *stream);
|
||||||
|
void addPosition(uint8_t idx, uint16_t value, uint16_t angle);
|
||||||
|
uint16_t getPositionValue(uint8_t position);
|
||||||
|
uint16_t getPositionAngle(uint8_t position);
|
||||||
|
uint8_t getPositionCount();
|
||||||
|
bool isMoving() { return _isMoving; }
|
||||||
|
void setMoving(bool moving) { _isMoving=moving; }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Virtual functions
|
||||||
|
*/
|
||||||
|
virtual void print(Print *stream) {
|
||||||
|
(void)stream; // suppress compiler warnings
|
||||||
|
}
|
||||||
|
virtual ~Turntable() {} // Destructor
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Public static functions
|
||||||
|
*/
|
||||||
|
inline static bool exists(uint16_t id) { return get(id) != 0; }
|
||||||
|
static bool setPosition(uint16_t id, uint8_t position, uint8_t activity=0);
|
||||||
|
static uint8_t getPosition(uint16_t id);
|
||||||
|
static bool ttMoving(uint16_t id);
|
||||||
|
inline static Turntable *first() { return _firstTurntable; }
|
||||||
|
static bool printAll(Print *stream) {
|
||||||
|
bool gotOne = false;
|
||||||
|
for (Turntable *tto = _firstTurntable; tto != 0; tto = tto->_nextTurntable)
|
||||||
|
if (!tto->isHidden()) {
|
||||||
|
gotOne = true;
|
||||||
|
StringFormatter::send(stream, F("<I %d %d>\n"), tto->getId(), tto->getPosition());
|
||||||
|
}
|
||||||
|
return gotOne;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* EXTTTurntable - EX-Turntable device.
|
||||||
|
*
|
||||||
|
*************************************************************************************/
|
||||||
|
class EXTTTurntable : public Turntable {
|
||||||
|
private:
|
||||||
|
// EXTTTurntableData contains device specific data
|
||||||
|
struct EXTTTurntableData {
|
||||||
|
VPIN vpin;
|
||||||
|
} _exttTurntableData;
|
||||||
|
|
||||||
|
// Constructor
|
||||||
|
EXTTTurntable(uint16_t id, VPIN vpin);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Create function
|
||||||
|
static Turntable *create(uint16_t id, VPIN vpin);
|
||||||
|
void print(Print *stream) override;
|
||||||
|
VPIN getVpin() const { return _exttTurntableData.vpin; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// EX-Turntable specific code for setting position
|
||||||
|
bool setPositionInternal(uint8_t position, uint8_t activity) override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
* DCCTurntable - DCC accessory Turntable device.
|
||||||
|
*
|
||||||
|
*************************************************************************************/
|
||||||
|
class DCCTurntable : public Turntable {
|
||||||
|
private:
|
||||||
|
// Constructor
|
||||||
|
DCCTurntable(uint16_t id);
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Create function
|
||||||
|
static Turntable *create(uint16_t id);
|
||||||
|
void print(Print *stream) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// DCC specific code for setting position
|
||||||
|
bool setPositionInternal(uint8_t position, uint8_t activity=0) override;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
|
@ -150,7 +150,6 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||||
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
DCCWaveform::progTrack.setPowerMode(cmd[3]=='1'?POWERMODE::ON:POWERMODE::OFF);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
CommandDistributor::broadcastPower();
|
|
||||||
}
|
}
|
||||||
#if defined(EXRAIL_ACTIVE)
|
#if defined(EXRAIL_ACTIVE)
|
||||||
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
|
else if (cmd[1]=='R' && cmd[2]=='A' && cmd[3]=='2' ) { // Route activate
|
||||||
|
@ -188,6 +187,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
||||||
|
sendIntro(stream);
|
||||||
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
|
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
|
||||||
break;
|
break;
|
||||||
case 'M': // multithrottle
|
case 'M': // multithrottle
|
||||||
|
@ -496,10 +496,11 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||||
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
TrackManager::setJoin(true); // <1 JOIN> so we can drive loco away
|
||||||
DIAG(F("LocoCallback commit success"));
|
DIAG(F("LocoCallback commit success"));
|
||||||
stashStream->commit();
|
stashStream->commit();
|
||||||
CommandDistributor::broadcastPower();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WiThrottle::sendIntro(Print* stream) {
|
void WiThrottle::sendIntro(Print* stream) {
|
||||||
|
if (introSent) // sendIntro only once
|
||||||
|
return;
|
||||||
introSent=true;
|
introSent=true;
|
||||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||||
|
@ -570,7 +571,7 @@ void WiThrottle::sendRoutes(Print* stream) {
|
||||||
|
|
||||||
void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||||
int16_t locoid=myLocos[loco].cab;
|
int16_t locoid=myLocos[loco].cab;
|
||||||
int fkeys=29;
|
int fkeys=32; // upper limit (send functions 0 to 31)
|
||||||
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||||
|
|
||||||
#ifdef EXRAIL_ACTIVE
|
#ifdef EXRAIL_ACTIVE
|
||||||
|
@ -620,7 +621,7 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||||
int fstate=DCC::getFn(locoid,fKey);
|
int8_t fstate=DCC::getFn(locoid,fKey);
|
||||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
|
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,25 +74,39 @@ class NetworkClient {
|
||||||
public:
|
public:
|
||||||
NetworkClient(WiFiClient c) {
|
NetworkClient(WiFiClient c) {
|
||||||
wifi = c;
|
wifi = c;
|
||||||
|
inUse = true;
|
||||||
};
|
};
|
||||||
bool ok() {
|
bool active(byte clientId) {
|
||||||
return (inUse && wifi.connected());
|
if (!inUse)
|
||||||
};
|
|
||||||
bool recycle(WiFiClient c) {
|
|
||||||
|
|
||||||
if (inUse == true) return false;
|
|
||||||
|
|
||||||
// return false here until we have
|
|
||||||
// implemented a LRU timer
|
|
||||||
// if (LRU too recent) return false;
|
|
||||||
return false;
|
return false;
|
||||||
|
if(!wifi.connected()) {
|
||||||
|
DIAG(F("Remove client %d"), clientId);
|
||||||
|
CommandDistributor::forget(clientId);
|
||||||
|
wifi.stop();
|
||||||
|
inUse = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool recycle(WiFiClient c) {
|
||||||
|
if (wifi == c) {
|
||||||
|
if (inUse == true)
|
||||||
|
DIAG(F("WARNING: Duplicate"));
|
||||||
|
else
|
||||||
|
DIAG(F("Returning"));
|
||||||
|
inUse = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (inUse == false) {
|
||||||
wifi = c;
|
wifi = c;
|
||||||
inUse = true;
|
inUse = true;
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
WiFiClient wifi;
|
WiFiClient wifi;
|
||||||
bool inUse = true;
|
private:
|
||||||
|
bool inUse;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::vector<NetworkClient> clients; // a list to hold all clients
|
static std::vector<NetworkClient> clients; // a list to hold all clients
|
||||||
|
@ -150,6 +164,8 @@ bool WifiESP::setup(const char *SSid,
|
||||||
if (haveSSID && havePassword && !forceAP) {
|
if (haveSSID && havePassword && !forceAP) {
|
||||||
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
|
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
|
||||||
WiFi.mode(WIFI_STA);
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN); // Scan all channels so we find strongest
|
||||||
|
// (default in Wifi library is first match)
|
||||||
#ifdef SERIAL_BT_COMMANDS
|
#ifdef SERIAL_BT_COMMANDS
|
||||||
WiFi.setSleep(true);
|
WiFi.setSleep(true);
|
||||||
#else
|
#else
|
||||||
|
@ -163,7 +179,9 @@ bool WifiESP::setup(const char *SSid,
|
||||||
delay(500);
|
delay(500);
|
||||||
}
|
}
|
||||||
if (WiFi.status() == WL_CONNECTED) {
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
// DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
||||||
|
DIAG(F("Wifi in STA mode"));
|
||||||
|
LCD(7, F("IP: %s"), WiFi.localIP().toString().c_str());
|
||||||
wifiUp = true;
|
wifiUp = true;
|
||||||
} else {
|
} else {
|
||||||
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
|
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
|
||||||
|
@ -188,7 +206,7 @@ bool WifiESP::setup(const char *SSid,
|
||||||
if (!haveSSID || forceAP) {
|
if (!haveSSID || forceAP) {
|
||||||
// prepare all strings
|
// prepare all strings
|
||||||
String strSSID(forceAP ? SSid : "DCCEX_");
|
String strSSID(forceAP ? SSid : "DCCEX_");
|
||||||
String strPass(forceAP ? password : "PASS_");
|
String strPass( (forceAP && havePassword) ? password : "PASS_");
|
||||||
if (!forceAP) {
|
if (!forceAP) {
|
||||||
String strMac = WiFi.macAddress();
|
String strMac = WiFi.macAddress();
|
||||||
strMac.remove(0,9);
|
strMac.remove(0,9);
|
||||||
|
@ -209,8 +227,13 @@ bool WifiESP::setup(const char *SSid,
|
||||||
if (WiFi.softAP(strSSID.c_str(),
|
if (WiFi.softAP(strSSID.c_str(),
|
||||||
havePassword ? password : strPass.c_str(),
|
havePassword ? password : strPass.c_str(),
|
||||||
channel, false, 8)) {
|
channel, false, 8)) {
|
||||||
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
// DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
||||||
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
DIAG(F("Wifi in AP mode"));
|
||||||
|
LCD(5, F("Wifi: %s"), strSSID.c_str());
|
||||||
|
if (!havePassword)
|
||||||
|
LCD(6, F("PASS: %s"),strPass.c_str());
|
||||||
|
// DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
||||||
|
LCD(7, F("IP: %s"),WiFi.softAPIP().toString().c_str());
|
||||||
wifiUp = true;
|
wifiUp = true;
|
||||||
APmode = true;
|
APmode = true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -276,37 +299,26 @@ void WifiESP::loop() {
|
||||||
// really no good way to check for LISTEN especially in AP mode?
|
// really no good way to check for LISTEN especially in AP mode?
|
||||||
wl_status_t wlStatus;
|
wl_status_t wlStatus;
|
||||||
if (APmode || (wlStatus = WiFi.status()) == WL_CONNECTED) {
|
if (APmode || (wlStatus = WiFi.status()) == WL_CONNECTED) {
|
||||||
// loop over all clients and remove inactive
|
|
||||||
for (clientId=0; clientId<clients.size(); clientId++){
|
|
||||||
// check if client is there and alive
|
|
||||||
if(clients[clientId].inUse && !clients[clientId].wifi.connected()) {
|
|
||||||
DIAG(F("Remove client %d"), clientId);
|
|
||||||
CommandDistributor::forget(clientId);
|
|
||||||
clients[clientId].wifi.stop();
|
|
||||||
clients[clientId].inUse = false;
|
|
||||||
//Do NOT clients.erase(clients.begin()+clientId) as
|
|
||||||
//that would mix up clientIds for later.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (server->hasClient()) {
|
if (server->hasClient()) {
|
||||||
WiFiClient client;
|
WiFiClient client;
|
||||||
while (client = server->available()) {
|
while (client = server->available()) {
|
||||||
for (clientId=0; clientId<clients.size(); clientId++){
|
for (clientId=0; clientId<clients.size(); clientId++){
|
||||||
if (clients[clientId].recycle(client)) {
|
if (clients[clientId].recycle(client)) {
|
||||||
DIAG(F("Recycle client %d %s"), clientId, client.remoteIP().toString().c_str());
|
DIAG(F("Recycle client %d %s:%d"), clientId, client.remoteIP().toString().c_str(),client.remotePort());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (clientId>=clients.size()) {
|
if (clientId>=clients.size()) {
|
||||||
NetworkClient nc(client);
|
NetworkClient nc(client);
|
||||||
clients.push_back(nc);
|
clients.push_back(nc);
|
||||||
DIAG(F("New client %d, %s"), clientId, client.remoteIP().toString().c_str());
|
DIAG(F("New client %d, %s:%d"), clientId, client.remoteIP().toString().c_str(),client.remotePort());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// loop over all connected clients
|
// loop over all connected clients
|
||||||
|
// this removes as a side effect inactive clients when checking ::active()
|
||||||
for (clientId=0; clientId<clients.size(); clientId++){
|
for (clientId=0; clientId<clients.size(); clientId++){
|
||||||
if(clients[clientId].ok()) {
|
if(clients[clientId].active(clientId)) {
|
||||||
int len;
|
int len;
|
||||||
if ((len = clients[clientId].wifi.available()) > 0) {
|
if ((len = clients[clientId].wifi.available()) > 0) {
|
||||||
// read data from client
|
// read data from client
|
||||||
|
@ -344,7 +356,7 @@ void WifiESP::loop() {
|
||||||
}
|
}
|
||||||
// buffer filled, end with '\0' so we can use it as C string
|
// buffer filled, end with '\0' so we can use it as C string
|
||||||
buffer[count]='\0';
|
buffer[count]='\0';
|
||||||
if((unsigned int)clientId <= clients.size() && clients[clientId].ok()) {
|
if((unsigned int)clientId <= clients.size() && clients[clientId].active(clientId)) {
|
||||||
if (Diag::CMD || Diag::WITHROTTLE)
|
if (Diag::CMD || Diag::WITHROTTLE)
|
||||||
DIAG(F("SEND %d:%s"), clientId, buffer);
|
DIAG(F("SEND %d:%s"), clientId, buffer);
|
||||||
clients[clientId].wifi.write(buffer,count);
|
clients[clientId].wifi.write(buffer,count);
|
||||||
|
@ -377,8 +389,9 @@ void WifiESP::loop() {
|
||||||
// prio task. On core1 this is not a problem
|
// prio task. On core1 this is not a problem
|
||||||
// as there the wdt is disabled by the
|
// as there the wdt is disabled by the
|
||||||
// arduio IDE startup routines.
|
// arduio IDE startup routines.
|
||||||
if (xPortGetCoreID() == 0)
|
if (xPortGetCoreID() == 0) {
|
||||||
feedTheDog0();
|
feedTheDog0();
|
||||||
yield();
|
yield();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#endif //ESP32
|
#endif //ESP32
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/*
|
/*
|
||||||
|
* © 2022-2024 Paul M. Antoine
|
||||||
* © 2021 Fred Decker
|
* © 2021 Fred Decker
|
||||||
* © 2020-2022 Harald Barth
|
* © 2020-2022 Harald Barth
|
||||||
* © 2020-2022 Chris Harlow
|
* © 2020-2022 Chris Harlow
|
||||||
|
@ -68,7 +69,9 @@ Stream * WifiInterface::wifiStream;
|
||||||
#define NUM_SERIAL 3
|
#define NUM_SERIAL 3
|
||||||
#define SERIAL1 Serial3
|
#define SERIAL1 Serial3
|
||||||
#define SERIAL3 Serial5
|
#define SERIAL3 Serial5
|
||||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG)
|
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) \
|
||||||
|
|| defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) \
|
||||||
|
|| defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
|
||||||
#define NUM_SERIAL 2
|
#define NUM_SERIAL 2
|
||||||
#define SERIAL1 Serial6
|
#define SERIAL1 Serial6
|
||||||
#else
|
#else
|
||||||
|
@ -363,11 +366,17 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||||
}
|
}
|
||||||
ipString[ipLen]=ipChar;
|
ipString[ipLen]=ipChar;
|
||||||
}
|
}
|
||||||
|
#ifndef PRINT_IP_PORT_SINGLE_LINE
|
||||||
LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this
|
LCD(4,F("%s"),ipString); // There is not enough room on some LCDs to put a title to this
|
||||||
|
#else
|
||||||
|
LCD(4,F("%s:%d"),ipString,port); // *** Single IP:Port
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
// suck up anything after the IP.
|
// suck up anything after the IP.
|
||||||
if (!checkForOK(1000, true, false)) return WIFI_DISCONNECTED;
|
if (!checkForOK(1000, true, false)) return WIFI_DISCONNECTED;
|
||||||
|
#ifndef PRINT_IP_PORT_SINGLE_LINE
|
||||||
LCD(5,F("PORT=%d"),port);
|
LCD(5,F("PORT=%d"),port);
|
||||||
|
#endif
|
||||||
|
|
||||||
return WIFI_CONNECTED;
|
return WIFI_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
|
@ -167,6 +167,9 @@ The configuration file for DCC-EX Command Station
|
||||||
// * #define SCROLLMODE 2 is by row (move up 1 row at a time).
|
// * #define SCROLLMODE 2 is by row (move up 1 row at a time).
|
||||||
#define SCROLLMODE 1
|
#define SCROLLMODE 1
|
||||||
|
|
||||||
|
// Shows IP and Port on Display in a single line
|
||||||
|
// #define PRINT_IP_PORT_SINGLE_LINE
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
// DISABLE EEPROM
|
// DISABLE EEPROM
|
||||||
//
|
//
|
||||||
|
@ -191,6 +194,31 @@ The configuration file for DCC-EX Command Station
|
||||||
//
|
//
|
||||||
// #define DISABLE_PROG
|
// #define DISABLE_PROG
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DISABLE / ENABLE VDPY
|
||||||
|
//
|
||||||
|
// The Virtual display "VDPY" feature is by default enabled everywhere
|
||||||
|
// but on Uno and Nano. If you think you can fit it (for example
|
||||||
|
// having disabled some of the features above) you can enable it with
|
||||||
|
// ENABLE_VDPY. You can even disable it on all other CPUs with
|
||||||
|
// DISABLE_VDPY
|
||||||
|
//
|
||||||
|
// #define DISABLE_VDPY
|
||||||
|
// #define ENABLE_VDPY
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// DISABLE / ENABLE DIAG
|
||||||
|
//
|
||||||
|
// To diagose different errors, you can turn on differnet messages. This costs
|
||||||
|
// program memory which we do not have enough on the Uno and Nano, so it is
|
||||||
|
// by default DISABLED on those. If you think you can fit it (for example
|
||||||
|
// having disabled some of the features above) you can enable it with
|
||||||
|
// ENABLE_DIAG. You can even disable it on all other CPUs with
|
||||||
|
// DISABLE_DIAG
|
||||||
|
//
|
||||||
|
// #define DISABLE_DIAG
|
||||||
|
// #define ENABLE_DIAG
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
|
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
|
||||||
// is 127 and the first long address is 128. There are manufacturers which have
|
// is 127 and the first long address is 128. There are manufacturers which have
|
||||||
|
@ -202,6 +230,14 @@ The configuration file for DCC-EX Command Station
|
||||||
// We do not support to use the same address, for example 100(long) and 100(short)
|
// We do not support to use the same address, for example 100(long) and 100(short)
|
||||||
// at the same time, there must be a border.
|
// at the same time, there must be a border.
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Some newer 32bit microcontrollers boot very quickly, so powering on I2C and other
|
||||||
|
// peripheral devices at the same time may result in the CommandStation booting too
|
||||||
|
// quickly to detect them.
|
||||||
|
// To work around this, uncomment the STARTUP_DELAY line below and set a value in
|
||||||
|
// milliseconds that works for your environment, default is 3000 (3 seconds).
|
||||||
|
// #define STARTUP_DELAY 3000
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
|
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
|
||||||
|
@ -239,7 +275,10 @@ The configuration file for DCC-EX Command Station
|
||||||
// SAMD/SAMC and STM32 have up to 6.)
|
// SAMD/SAMC and STM32 have up to 6.)
|
||||||
// To monitor a throttle on one or more serial ports, uncomment the defines below.
|
// To monitor a throttle on one or more serial ports, uncomment the defines below.
|
||||||
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
|
// NOTE: do not define here the WiFi shield serial port or your wifi will not work.
|
||||||
//
|
// -------------------------------------
|
||||||
|
// For Use with FastClock serial: uncomment the needed serial Port and
|
||||||
|
// FastClock will work, no further actions are needed
|
||||||
|
// -------------------------------------
|
||||||
//#define SERIAL1_COMMANDS
|
//#define SERIAL1_COMMANDS
|
||||||
//#define SERIAL2_COMMANDS
|
//#define SERIAL2_COMMANDS
|
||||||
//#define SERIAL3_COMMANDS
|
//#define SERIAL3_COMMANDS
|
||||||
|
@ -247,6 +286,17 @@ The configuration file for DCC-EX Command Station
|
||||||
//#define SERIAL5_COMMANDS
|
//#define SERIAL5_COMMANDS
|
||||||
//#define SERIAL6_COMMANDS
|
//#define SERIAL6_COMMANDS
|
||||||
//
|
//
|
||||||
|
// -------------------------------------
|
||||||
|
// FastClock with I2C
|
||||||
|
// uncomment the following Line and Set the used I2C Address
|
||||||
|
//#define FAST_CLOCK_I2C 0x55 // default is 0x55
|
||||||
|
// -------------------------------------
|
||||||
|
//
|
||||||
|
// -------------------------------------
|
||||||
|
// FastClock in HH:MM on Display
|
||||||
|
//#define FASTCLOCK_READABLE
|
||||||
|
// -------------------------------------
|
||||||
|
//
|
||||||
// BLUETOOTH SERIAL ON ESP32
|
// BLUETOOTH SERIAL ON ESP32
|
||||||
// On ESP32 you have the possibility to use the builtin BT serial to connect to
|
// On ESP32 you have the possibility to use the builtin BT serial to connect to
|
||||||
// the CS.
|
// the CS.
|
||||||
|
@ -266,6 +316,12 @@ The configuration file for DCC-EX Command Station
|
||||||
//
|
//
|
||||||
//#define SERIAL_BT_COMMANDS
|
//#define SERIAL_BT_COMMANDS
|
||||||
|
|
||||||
|
// BOOSTER PIN INPUT ON ESP32
|
||||||
|
// On ESP32 you have the possibility to define a pin as booster input
|
||||||
|
// Arduio pin D2 is GPIO 26 on ESPDuino32
|
||||||
|
//
|
||||||
|
//#define BOOSTER_INPUT 26
|
||||||
|
|
||||||
// SABERTOOTH
|
// SABERTOOTH
|
||||||
//
|
//
|
||||||
// This is a very special option and only useful if you happen to have a
|
// This is a very special option and only useful if you happen to have a
|
||||||
|
|
24
defines.h
24
defines.h
|
@ -144,9 +144,9 @@
|
||||||
#define DISABLE_EEPROM
|
#define DISABLE_EEPROM
|
||||||
#endif
|
#endif
|
||||||
// STM32 support for native I2C is awaiting development
|
// STM32 support for native I2C is awaiting development
|
||||||
#ifndef I2C_USE_WIRE
|
// #ifndef I2C_USE_WIRE
|
||||||
#define I2C_USE_WIRE
|
// #define I2C_USE_WIRE
|
||||||
#endif
|
// #endif
|
||||||
|
|
||||||
/* TODO when ready
|
/* TODO when ready
|
||||||
#elif defined(ARDUINO_ARCH_RP2040)
|
#elif defined(ARDUINO_ARCH_RP2040)
|
||||||
|
@ -213,6 +213,24 @@
|
||||||
//
|
//
|
||||||
#define WIFI_SERIAL_LINK_SPEED 115200
|
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
|
||||||
|
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
|
||||||
|
//
|
||||||
|
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
|
||||||
|
#define IO_NO_HAL // HAL too big whatever you disable otherwise
|
||||||
|
|
||||||
|
#ifndef ENABLE_VDPY
|
||||||
|
#define DISABLE_VDPY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ENABLE_DIAG
|
||||||
|
#define DISABLE_DIAG
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
#if __has_include ( "myAutomation.h")
|
#if __has_include ( "myAutomation.h")
|
||||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
|
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
|
||||||
#define EXRAIL_ACTIVE
|
#define EXRAIL_ACTIVE
|
||||||
|
|
BIN
images/IMG_5870_1.jpg
Normal file
BIN
images/IMG_5870_1.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 741 KiB |
|
@ -25,6 +25,7 @@
|
||||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||||
//#include "IO_EXFastClock.h" // FastClock driver
|
//#include "IO_EXFastClock.h" // FastClock driver
|
||||||
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
|
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
|
||||||
|
//#include "IO_I2CDFPlayer.h" // DFPlayer over I2C
|
||||||
|
|
||||||
//==========================================================================
|
//==========================================================================
|
||||||
// The function halSetup() is invoked from CS if it exists within the build.
|
// The function halSetup() is invoked from CS if it exists within the build.
|
||||||
|
@ -234,6 +235,31 @@ void halSetup() {
|
||||||
// DFPlayer::create(10000, 10, Serial1);
|
// DFPlayer::create(10000, 10, Serial1);
|
||||||
|
|
||||||
|
|
||||||
|
//=======================================================================
|
||||||
|
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module on a SC16IS750/SC16IS752 I2C UART
|
||||||
|
//=======================================================================
|
||||||
|
// DFPlayer via NXP SC16IS752 I2C Dual UART.
|
||||||
|
// I2C address range 0x48 - 0x57
|
||||||
|
//
|
||||||
|
// Generic format:
|
||||||
|
// I2CDFPlayer::create(1st vPin, vPins, I2C address, xtal);
|
||||||
|
// Parameters:
|
||||||
|
// 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||||
|
// vPins : Total number of virtual pins allocated (1 vPin is supported currently)
|
||||||
|
// 1st vPin for UART 0
|
||||||
|
// I2C Address : I2C address of the serial controller, in 0x format
|
||||||
|
// xtal : 0 for 1.8432Mhz, 1 for 14.7456Mhz
|
||||||
|
//
|
||||||
|
// The vPin is also a pin that can be read with the WAITFOR(vPin) command indicating if the DFPlayer has finished playing a track
|
||||||
|
//
|
||||||
|
|
||||||
|
// I2CDFPlayer::create(10000, 1, 0x48, 1);
|
||||||
|
//
|
||||||
|
// Configuration example on a multiplexer
|
||||||
|
// I2CDFPlayer::create(10000, 1, {I2CMux_0, SubBus_0, 0x48}, 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
// 16-pad capacitative touch key pad based on TP229 IC.
|
// 16-pad capacitative touch key pad based on TP229 IC.
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
|
@ -285,12 +311,13 @@ void halSetup() {
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
// The parameters are:
|
// The parameters are:
|
||||||
// firstVpin = First available Vpin to allocate
|
// firstVpin = First available Vpin to allocate
|
||||||
// numPins= Number of Vpins to allocate, can be either 1 or 2
|
// numPins= Number of Vpins to allocate, can be either 1 to 3
|
||||||
// i2cAddress = Available I2C address (default 0x70)
|
// i2cAddress = Available I2C address (default 0x67)
|
||||||
|
|
||||||
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
|
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
|
||||||
//RotaryEncoder::create(700, 1, 0x70);
|
//RotaryEncoder::create(700, 1, 0x67);
|
||||||
//RotaryEncoder::create(701, 2, 0x71);
|
//RotaryEncoder::create(700, 2, 0x67);
|
||||||
|
//RotaryEncoder::create(700, 3, 0x67);
|
||||||
|
|
||||||
//=======================================================================
|
//=======================================================================
|
||||||
// The following directive defines an EX-FastClock instance.
|
// The following directive defines an EX-FastClock instance.
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
default_envs =
|
default_envs =
|
||||||
mega2560
|
mega2560
|
||||||
uno
|
uno
|
||||||
mega328
|
|
||||||
unowifiR2
|
unowifiR2
|
||||||
nano
|
nano
|
||||||
samd21-dev-usb
|
samd21-dev-usb
|
||||||
|
@ -30,8 +29,7 @@ include_dir = .
|
||||||
|
|
||||||
[env]
|
[env]
|
||||||
build_flags = -Wall -Wextra
|
build_flags = -Wall -Wextra
|
||||||
monitor_filters = time
|
; monitor_filters = time
|
||||||
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
|
|
||||||
|
|
||||||
[env:samd21-dev-usb]
|
[env:samd21-dev-usb]
|
||||||
platform = atmelsam
|
platform = atmelsam
|
||||||
|
@ -60,7 +58,7 @@ framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
|
build_flags = -std=c++17
|
||||||
|
|
||||||
[env:mega2560-debug]
|
[env:mega2560-debug]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
|
@ -108,7 +106,7 @@ lib_deps =
|
||||||
SPI
|
SPI
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
build_flags = ; -DDIAG_LOOPTIMES
|
build_flags =
|
||||||
|
|
||||||
[env:mega328]
|
[env:mega328]
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
|
@ -150,10 +148,7 @@ build_flags =
|
||||||
platform = atmelavr
|
platform = atmelavr
|
||||||
board = uno
|
board = uno
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps =
|
lib_deps = ${env.lib_deps}
|
||||||
${env.lib_deps}
|
|
||||||
arduino-libraries/Ethernet
|
|
||||||
SPI
|
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
build_flags = -mcall-prologues
|
build_flags = -mcall-prologues
|
||||||
|
@ -166,9 +161,14 @@ framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
|
build_flags = -mcall-prologues
|
||||||
|
|
||||||
[env:ESP32]
|
[env:ESP32]
|
||||||
platform = espressif32
|
; Lock version to 6.7.0 as that is
|
||||||
|
; Arduino v2.0.16 (based on IDF v4.4.7)
|
||||||
|
; which is the latest version based
|
||||||
|
; on IDF v4. We can not use IDF v5.
|
||||||
|
platform = espressif32 @ 6.7.0
|
||||||
board = esp32dev
|
board = esp32dev
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
|
@ -190,10 +190,75 @@ platform = ststm32
|
||||||
board = nucleo_f446re
|
board = nucleo_f446re
|
||||||
framework = arduino
|
framework = arduino
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
|
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||||
monitor_speed = 115200
|
monitor_speed = 115200
|
||||||
monitor_echo = yes
|
monitor_echo = yes
|
||||||
|
|
||||||
|
; Experimental - no reason this should not work, but not
|
||||||
|
; tested as yet
|
||||||
|
;
|
||||||
|
[env:Nucleo-F401RE]
|
||||||
|
platform = ststm32
|
||||||
|
board = nucleo_f401re
|
||||||
|
framework = arduino
|
||||||
|
lib_deps = ${env.lib_deps}
|
||||||
|
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||||
|
monitor_speed = 115200
|
||||||
|
monitor_echo = yes
|
||||||
|
|
||||||
|
; Commented out by default as the F13ZH has variant files
|
||||||
|
; but NOT the nucleo_f413zh.json file which needs to be
|
||||||
|
; installed before you can let PlatformIO see this
|
||||||
|
;
|
||||||
|
; [env:Nucleo-F413ZH]
|
||||||
|
; platform = ststm32
|
||||||
|
; board = nucleo_f413zh
|
||||||
|
; framework = arduino
|
||||||
|
; lib_deps = ${env.lib_deps}
|
||||||
|
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||||
|
; monitor_speed = 115200
|
||||||
|
; monitor_echo = yes
|
||||||
|
|
||||||
|
; Commented out by default as the F446ZE needs variant files
|
||||||
|
; installed before you can let PlatformIO see this
|
||||||
|
;
|
||||||
|
; [env:Nucleo-F446ZE]
|
||||||
|
; platform = ststm32
|
||||||
|
; board = nucleo_f446ze
|
||||||
|
; framework = arduino
|
||||||
|
; lib_deps = ${env.lib_deps}
|
||||||
|
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||||
|
; monitor_speed = 115200
|
||||||
|
; monitor_echo = yes
|
||||||
|
|
||||||
|
; Commented out by default as the F412ZG needs variant files
|
||||||
|
; installed before you can let PlatformIO see this
|
||||||
|
;
|
||||||
|
; [env:Nucleo-F412ZG]
|
||||||
|
; platform = ststm32
|
||||||
|
; board = blah_f412zg
|
||||||
|
; framework = arduino
|
||||||
|
; lib_deps = ${env.lib_deps}
|
||||||
|
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||||
|
; monitor_speed = 115200
|
||||||
|
; monitor_echo = yes
|
||||||
|
; upload_protocol = stlink
|
||||||
|
|
||||||
|
; Experimental - Ethernet work still in progress
|
||||||
|
;
|
||||||
|
; [env:Nucleo-F429ZI]
|
||||||
|
; platform = ststm32
|
||||||
|
; board = nucleo_f429zi
|
||||||
|
; framework = arduino
|
||||||
|
; lib_deps = ${env.lib_deps}
|
||||||
|
; arduino-libraries/Ethernet @ ^2.0.1
|
||||||
|
; stm32duino/STM32Ethernet @ ^1.3.0
|
||||||
|
; stm32duino/STM32duino LwIP @ ^2.1.2
|
||||||
|
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||||
|
; monitor_speed = 115200
|
||||||
|
; monitor_echo = yes
|
||||||
|
; upload_protocol = stlink
|
||||||
|
|
||||||
[env:Teensy3_2]
|
[env:Teensy3_2]
|
||||||
platform = teensy
|
platform = teensy
|
||||||
board = teensy31
|
board = teensy31
|
||||||
|
@ -233,4 +298,3 @@ framework = arduino
|
||||||
build_flags = -std=c++17 -Os -g2
|
build_flags = -std=c++17 -Os -g2
|
||||||
lib_deps = ${env.lib_deps}
|
lib_deps = ${env.lib_deps}
|
||||||
lib_ignore =
|
lib_ignore =
|
||||||
|
|
||||||
|
|
136
version.h
136
version.h
|
@ -3,16 +3,132 @@
|
||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "5.0.9"
|
#define VERSION "5.2.60"
|
||||||
// 5.0.9 - EX-IOExpander bug fix for memory allocation
|
// 5.2.60 - Bugfix: Opcode AFTEROVERLOAD does not have an argument that is a pin and needs to be initialized
|
||||||
// - EX-IOExpander bug fix to allow for devices with no analogue or no digital pins
|
// - Remove inrush throttle after half good time so that we go to mode overload if problem persists
|
||||||
// 5.0.8 - Bugfix: Do not crash on turnouts without description
|
// 5.2.59 - STM32 bugfix correct Serial1 definition for Nucleo-F401RE
|
||||||
// 5.0.7 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
|
// - STM32 add support for ARDUINO_NUCLEO_F4X9ZI type to span F429/F439 in upcoming STM32duino release v2.8 as a result of our PR
|
||||||
// 5.0.6 - Bugfix lost TURNOUTL description
|
// 5.2.58 - EXRAIL ALIAS allows named pins
|
||||||
// 5.0.5 - Bugfix version detection logic and better message
|
// 5.2.57 - Bugfix autoreverse: Apply mode by binart bit match and not by equality
|
||||||
// 5.0.4 - Bugfix: <JR> misses default roster.
|
// 5.2.56 - Bugfix and refactor for EXRAIL getSignalSlot
|
||||||
// 5.0.3 - Check bad AT firmware version
|
// 5.2.55 - Move EXRAIL isSignal() to public to allow use in STEALTH call
|
||||||
// 5.0.2 - Bugfix: ESP32 30ms off time
|
// 5.2.54 - Bugfix for EXRAIL signal handling for active high
|
||||||
|
// 5.2.53 - Bugfix for EX-Fastclock, call I2CManager.begin() before checking I2C address
|
||||||
|
// 5.2.52 - Bugfix for ADCee() to handle ADC2 and ADC3 channel inputs on F446ZE and others
|
||||||
|
// - Add support for ports G and H on STM32 for ADCee() and MotorDriver pins/shadow regs
|
||||||
|
// 5.2.51 - Bugfix for SIGNAL: Distinguish between sighandle and sigid
|
||||||
|
// 5.2.50 - EXRAIL ONBUTTON/ONSENSOR observe LATCH
|
||||||
|
// 5.2.49 - EXRAIL additions:
|
||||||
|
// ONBUTTON, ONSENSOR
|
||||||
|
// 5.2.48 - Bugfix: HALDisplay was generating I2C traffic prior to I2C being initialised
|
||||||
|
// 5.2.47 - EXRAIL additions:
|
||||||
|
// STEALTH_GLOBAL
|
||||||
|
// BLINK
|
||||||
|
// TOGGLE_TURNOUT
|
||||||
|
// FTOGGLE, XFTOGGLE
|
||||||
|
// Reduced code-developmenmt DIAG noise
|
||||||
|
// 5.2.46 - Support for extended consist CV20 in <R> and <W id>
|
||||||
|
// - New cmd <W CONSIST id [REVERSE]> to handle long/short consist ids
|
||||||
|
// 5.2.45 - ESP32 Trackmanager reset cab number to 0 when track is not DC
|
||||||
|
// ESP32 fix PWM LEDC inverted pin mode
|
||||||
|
// ESP32 rewrite PWM LEDC to use pin mux
|
||||||
|
// 5.2.42 - ESP32 Bugfix: Uninitialized stack variable
|
||||||
|
// 5.2.41 - Update rotary encoder default address to 0x67
|
||||||
|
// 5.2.40 - Allow no shield
|
||||||
|
// 5.2.39 - Functions for DC frequency: Use func up to F31
|
||||||
|
// 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.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
|
||||||
|
// - Added UART detection to I2CManager.cpp
|
||||||
|
// 5.2.28 - ESP32: Can all Wifi channels.
|
||||||
|
// - ESP32: Only write Wifi password to display if it is a well known one
|
||||||
|
// 5.2.27 - Bugfix: IOExpander memory allocation
|
||||||
|
// 5.2.26 - Silently ignore overridden HAL defaults
|
||||||
|
// - include HAL_IGNORE_DEFAULTS macro in EXRAIL
|
||||||
|
// 5.2.25 - Fix bug causing <X> after working <D commands
|
||||||
|
// 5.2.24 - Exrail macro asserts to catch
|
||||||
|
// : duplicate/missing automation/route/sequence/call ids
|
||||||
|
// : latches and reserves out of range
|
||||||
|
// : speeds out of range
|
||||||
|
// 5.2.23 - KeywordHasher _hk (no functional change)
|
||||||
|
// 5.2.22 - Bugfixes: Empty turnout descriptions ok; negative route numbers valid.
|
||||||
|
// 5.2.21 - Add STARTUP_DELAY config option to delay CS bootup
|
||||||
|
// 5.2.20 - Check return of Ethernet.begin()
|
||||||
|
// 5.2.19 - ESP32: Determine if the RMT hardware can handle DCC
|
||||||
|
// 5.2.18 - Display network IP fix
|
||||||
|
// 5.2.17 - ESP32 simplify network logic
|
||||||
|
// 5.2.16 - Bugfix to allow for devices using the EX-IOExpander protocol to have no analogue or no digital pins
|
||||||
|
// 5.2.15 - move call to CommandDistributor::broadcastPower() into the TrackManager::setTrackPower(*) functions
|
||||||
|
// - add repeats to function packets that are not reminded in accordance with accessory packets
|
||||||
|
// 5.2.14 - Reminder window DCC packet optimization
|
||||||
|
// - Optional #define DISABLE_FUNCTION_REMINDERS
|
||||||
|
// 5.2.13 - EXRAIL STEALTH
|
||||||
|
// 5.2.12 - ESP32 add AP mode LCD messages with SSID/PW for
|
||||||
|
// - STM32 change to UID_BASE constants in DCCTimerSTM32 rather than raw hex addresses for UID registers
|
||||||
|
// - STM32 extra UART/USARTs for larger Nucleo models
|
||||||
|
// 5.2.11 - Change from TrackManager::returnMode to TrackManager::getMode
|
||||||
|
// 5.2.10 - Include trainbrains.eu block unoccupancy driver
|
||||||
|
// - include IO_PCA9555
|
||||||
|
// 5.2.9 - Bugfix LCD startup with no LCD, uses <@
|
||||||
|
// 5.2.9 - EXRAIL STASH feature
|
||||||
|
// 5.2.8 - Bugfix: Do not turn off all tracks on change
|
||||||
|
// give better power messages
|
||||||
|
// 5.2.7 - Bugfix: EXRAIL ling segment
|
||||||
|
// - Bugfix: Back out wrongly added const
|
||||||
|
// - Bugfix ESP32: Do not inverse DCX direction signal twice
|
||||||
|
// 5.2.6 - Trackmanager broadcast power state on track mode change
|
||||||
|
// 5.2.5 - Trackmanager: Do not treat TRACK_MODE_ALL as TRACK_MODE_DC
|
||||||
|
// 5.2.4 - LCD macro will not do diag if that duplicates @ to same target.
|
||||||
|
// - Added ROUTE_DISABLED macro in EXRAIL
|
||||||
|
// 5.2.3 - Bugfix: Catch stange input to parser
|
||||||
|
// 5.2.2 - Added option to allow MAX_CHARACTER_ROWS to be defined in config.h
|
||||||
|
// 5.2.1 - Trackmanager rework for simpler structure
|
||||||
|
// 5.2.0 - ESP32: Autoreverse and booster mode support
|
||||||
|
// 5.1.21 - EXRAIL invoke multiple ON handlers for same event
|
||||||
|
// 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION
|
||||||
|
// 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
|
||||||
|
// 5.1.18 - TURNOUTL bugfix
|
||||||
|
// 5.1.17 - Divide out C for config and D for diag commands
|
||||||
|
// 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create
|
||||||
|
// 5.1.15 - LCC/Adapter support and Exrail feature-compile-out.
|
||||||
|
// 5.1.14 - Fixed IFTTPOSITION
|
||||||
|
// 5.1.13 - Changed turntable broadcast from i to I due to server string conflict
|
||||||
|
// 5.1.12 - Added Power commands <0 A> & <1 A> etc. and update to <=>
|
||||||
|
// Added EXRAIL SET_POWER(track, ON/OFF)
|
||||||
|
// Fixed a problem whereby <1 MAIN> also powered on PROG track
|
||||||
|
// Added functions to TrackManager.cpp to allow UserAddin code for power display on OLED/LCD
|
||||||
|
// Added - returnMode(byte t), returnDCAddr(byte t) & getModeName(byte Mode)
|
||||||
|
// 5.1.11 - STM32F4xx revised I2C clock setup, no correctly sets clock and has fully variable frequency selection
|
||||||
|
// 5.1.10 - STM32F4xx DCCEXanalogWrite to handle PWM generation for TrackManager DC/DCX
|
||||||
|
// - STM32F4xx DCC 58uS timer now using non-PWM output timers where possible
|
||||||
|
// - ESP32 brakeCanPWM check now detects UNUSED_PIN
|
||||||
|
// - ARM architecture brakeCanPWM now uses digitalPinHasPWM()
|
||||||
|
// - STM32F4xx shadowpin extensions to handle pins on ports D, E and F
|
||||||
|
// 5.1.9 - Fixed IO_PCA9555'h to work with PCA9548 mux, tested OK
|
||||||
|
// 5.1.8 - STM32Fxx ADCee extension to support ADCs #2 and #3
|
||||||
|
// 5.1.7 - Fix turntable broadcasts for non-movement activities and <JP> result
|
||||||
|
// 5.1.6 - STM32F4xx native I2C driver added
|
||||||
|
// 5.1.5 - Added turntable object and EXRAIL commands
|
||||||
|
// - <I ...>, <JO ...>, <JP ...> - turntable commands
|
||||||
|
// - DCC_TURNTABLE, EXTT_TURNTABLE, IFTTPOSITION, ONROTATE, ROTATE, ROTATE_DCC, TT_ADDPOSITION, WAITFORTT EXRAIL
|
||||||
|
// 5.1.4 - Added ONOVERLOAD & AFTEROVERLOAD to EXRAIL
|
||||||
|
// 5.1.3 - Make parser more fool proof
|
||||||
|
// 5.1.2 - Bugfix: ESP32 30ms off time
|
||||||
|
// 5.1.1 - Check bad AT firmware version
|
||||||
|
// - Update IO_PCA9555.h reflecting IO_MCP23017.h changes to support PCA9548 mux
|
||||||
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
|
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
|
||||||
// 5.0.0 - Make 4.2.69 the 5.0.0 release
|
// 5.0.0 - Make 4.2.69 the 5.0.0 release
|
||||||
// 4.2.69 - Bugfix: Make <!> work in DC mode
|
// 4.2.69 - Bugfix: Make <!> work in DC mode
|
||||||
|
|
Loading…
Reference in New Issue
Block a user