1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-31 11:23:44 +02:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Neil McKechnie
fafd3cbc04 Enable multiple user-defined servo profiles 2023-04-19 00:02:23 +01:00
53 changed files with 721 additions and 2312 deletions

View File

@@ -2,7 +2,6 @@
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* © 2022 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -168,7 +167,7 @@ void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
// be safe for both types.
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("PFT%l<;>%d\n"), (int32_t)time*60, rate);
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate);
#endif
}
@@ -205,39 +204,6 @@ int16_t CommandDistributor::retClockTime() {
void CommandDistributor::broadcastLoco(byte slot) {
DCC::LOCO * sp=&DCC::speedTable[slot];
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
#ifdef SABERTOOTH
if (Serial2 && sp->loco == SABERTOOTH) {
static uint8_t rampingmode = 0;
bool direction = (sp->speedCode & 0x80) !=0; // true for forward
int32_t speed = sp->speedCode & 0x7f;
if (speed == 1) { // emergency stop
if (rampingmode != 1) {
rampingmode = 1;
Serial2.print("R1: 0\r\n");
Serial2.print("R2: 0\r\n");
}
Serial2.print("MD: 0\r\n");
} else {
if (speed != 0) {
// speed is here 2 to 127
speed = (speed - 1) * 1625 / 100;
speed = speed * (direction ? 1 : -1);
// speed is here -2047 to 2047
}
if (rampingmode != 2) {
rampingmode = 2;
Serial2.print("R1: 2047\r\n");
Serial2.print("R2: 2047\r\n");
}
Serial2.print("M1: ");
Serial2.print(speed);
Serial2.print("\r\n");
Serial2.print("M2: ");
Serial2.print(speed);
Serial2.print("\r\n");
}
}
#endif
#ifdef CD_HANDLE_RING
WiThrottle::markForBroadcast(sp->loco);
#endif
@@ -261,8 +227,11 @@ void CommandDistributor::broadcastPower() {
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
}
void CommandDistributor::broadcastRaw(clientType type, char * msg) {
broadcastReply(type, F("%s"),msg);
void CommandDistributor::broadcastText(const FSH * msg) {
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg);
#ifdef CD_HANDLE_RING
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
#endif
}
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {

View File

@@ -2,8 +2,6 @@
* © 2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2020 Gregor Baues
* © 2022 Colin Murdoch
*
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -35,9 +33,8 @@
#endif
class CommandDistributor {
public:
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
private:
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
static void broadcastToClients(clientType type);
static StringBuffer * broadcastBufferWriter;
#ifdef CD_HANDLE_RING
@@ -53,7 +50,7 @@ public :
static void setClockTime(int16_t time, int8_t rate, byte opt);
static int16_t retClockTime();
static void broadcastPower();
static void broadcastRaw(clientType type,char * msg);
static void broadcastText(const FSH * msg);
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId);

View File

@@ -30,7 +30,6 @@
* © 2021 Neil McKechnie
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -76,15 +75,6 @@ void setup()
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
IODevice::begin();
// As the setup of a motor shield may require a read of the current sense input from the ADC,
// let's make sure to initialise the ADCee class!
ADCee::begin();
// Set up MotorDrivers early to initialize all pins
TrackManager::Setup(MOTOR_SHIELD_TYPE);
DISPLAY_START (
// This block is still executed for DIAGS if display not in use
LCD(0,F("DCC-EX v%S"),F(VERSION));
@@ -96,19 +86,29 @@ void setup()
// Start Ethernet if it exists
#ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
#endif // WIFI_ON
#else
// ESP32 needs wifi on always
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);
#endif // ARDUINO_ARCH_ESP32
#if ETHERNET_ON
EthernetInterface::setup();
#endif // ETHERNET_ON
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
IODevice::begin();
// As the setup of a motor shield may require a read of the current sense input from the ADC,
// let's make sure to initialise the ADCee class!
ADCee::begin();
// Responsibility 3: Start the DCC engine.
DCC::begin();
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
// Standard supported devices have pre-configured macros but custome hardware installations require
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
TrackManager::Setup(MOTOR_SHIELD_TYPE);
// Start RMFT aka EX-RAIL (ignored if no automnation)
RMFT::begin();

View File

@@ -60,7 +60,8 @@ const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL;
byte DCC::globalSpeedsteps=128;
void DCC::begin() {
void DCC::begin(const FSH * motorShieldName) {
shieldName=(FSH *)motorShieldName;
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
#ifndef DISABLE_EEPROM
// Load stuff from EEprom
@@ -692,7 +693,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
if (loco==0) {
// broadcast stop/estop but dont change direction
for (int reg = 0; reg <= highestUsedReg; reg++) {
for (int reg = 0; reg < highestUsedReg; reg++) {
if (speedTable[reg].loco==0) continue;
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
if (speedTable[reg].speedCode != newspeed) {

5
DCC.h
View File

@@ -51,10 +51,7 @@ const byte MAX_LOCOS = 30;
class DCC
{
public:
static inline void setShieldName(const FSH * motorShieldName) {
shieldName=(FSH *)motorShieldName;
};
static void begin();
static void begin(const FSH * motorShieldName);
static void loop();
// Public DCC API functions

View File

@@ -152,7 +152,7 @@ byte DCCACK::getAck() {
return(0); // pending set off but not detected means no ACK.
}
#ifndef DISABLE_PROG
void DCCACK::loop() {
while (ackManagerProg) {
byte opcode=GETFLASH(ackManagerProg);
@@ -351,7 +351,7 @@ void DCCACK::callback(int value) {
switch (callbackState) {
case AFTER_READ:
if (ackManagerRejoin && !autoPowerOff) {
if (ackManagerRejoin && autoPowerOff) {
progDriver->setPower(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;
@@ -414,7 +414,7 @@ void DCCACK::callback(int value) {
(ackManagerCallback)( value);
}
}
#endif
void DCCACK::checkAck(byte sentResetsSincePacket) {
if (!ackPending) return;

View File

@@ -3,11 +3,10 @@
* © 2021 Neil McKechnie
* © 2021 Mike S
* © 2021 Herb Morton
* © 2020-2023 Harald Barth
* © 2020-2022 Harald Barth
* © 2020-2021 M Steve Todd
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2022 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -25,79 +24,6 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
List of single character OPCODEs in use for reference.
When determining a new OPCODE for a new feature, refer to this list as the source of truth.
Once a new OPCODE is decided upon, update this list.
Character, Usage
/, |EX-R| interactive commands
-, Remove from reminder table
=, |TM| configuration
!, Emergency stop
@, Reserved for future use - LCD messages to JMRI
#, Request number of supported cabs/locos; heartbeat
+, WiFi AT commands
?, Reserved for future use
0, Track power off
1, Track power on
a, DCC accessory control
A,
b, Write CV bit on main
B, Write CV bit
c, Request current command
C,
d,
D, Diagnostic commands
e, Erase EEPROM
E, Store configuration in EEPROM
f, Loco decoder function control (deprecated)
F, Loco decoder function control
g,
G,
h,
H, Turnout state broadcast
i, Reserved for future use - Turntable object broadcast
I, Reserved for future use - Turntable object command and control
j, Throttle responses
J, Throttle queries
k, Reserved for future use - Potentially Railcom
K, Reserved for future use - Potentially Railcom
l, Loco speedbyte/function map broadcast
L,
m,
M, Write DCC packet
n,
N,
o,
O, Output broadcast
p, Broadcast power state
P, Write DCC packet
q, Sensor deactivated
Q, Sensor activated
r, Broadcast address read on programming track
R, Read CVs
s, Display status
S, Sensor configuration
t, Cab/loco update command
T, Turnout configuration/control
u, Reserved for user commands
U, Reserved for user commands
v,
V, Verify CVs
w, Write CV on main
W, Write CV
x,
X, Invalid command
y,
Y, Output broadcast
z,
Z, Output configuration/control
*/
#include "StringFormatter.h"
#include "DCCEXParser.h"
#include "DCC.h"
@@ -120,14 +46,16 @@ Once a new OPCODE is decided upon, update this list.
#define SENDFLASHLIST(stream,flashList) \
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==INT16_MAX) break; \
StringFormatter::send(stream,F(" %d"),value); \
if (value==0) break; \
StringFormatter::send(stream,F(" %d"),value); \
}
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
const int16_t HASH_KEYWORD_PROG = -29718;
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_JOIN = -30750;
const int16_t HASH_KEYWORD_CABS = -11981;
const int16_t HASH_KEYWORD_RAM = 25982;
const int16_t HASH_KEYWORD_CMD = 9962;
@@ -135,11 +63,7 @@ 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
@@ -162,7 +86,6 @@ 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_HBRIDGE=-20585;
const int16_t HASH_KEYWORD_SHOW = -21309;
const int16_t HASH_KEYWORD_ANIN = -10424;
const int16_t HASH_KEYWORD_ANOUT = -26399;
@@ -293,9 +216,6 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
{
#ifdef DISABLE_PROG
(void)ringStream;
#endif
#ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused
#endif
@@ -365,8 +285,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (direction < 0 || direction > 1)
break; // invalid direction code
if (cab > 10239 || cab < 0)
break; // beyond DCC range
DCC::setThrottle(cab, tspeed, direction);
if (params == 4) // send obsolete format T response
@@ -450,7 +368,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return;
break;
#ifndef DISABLE_PROG
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
DCC::writeCVByteMain(p[0], p[1], p[2]);
return;
@@ -458,12 +375,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return;
#endif
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
#ifndef DISABLE_PROG
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
#endif
// NOTE: this command was parsed in HEX instead of decimal
params--; // drop REG
if (params<1) break;
@@ -478,7 +392,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
return;
#ifndef DISABLE_PROG
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p, ringStream))
break;
@@ -536,7 +449,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
return;
}
break;
#endif
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{
@@ -544,29 +456,27 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool prog=false;
bool join=false;
if (params > 1) break;
if (params==0) { // All
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
#ifndef DISABLE_PROG
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true;
prog=true;
join=true;
}
else if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true;
}
#endif
else break; // will reply <X>
}
TrackManager::setJoin(join);
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(join);
CommandDistributor::broadcastPower();
return;
@@ -577,7 +487,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool main=false;
bool prog=false;
if (params > 1) break;
if (params==0) { // All
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
main=true;
prog=true;
}
@@ -585,20 +495,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
}
#ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
}
#endif
else break; // will reply <X>
}
TrackManager::setJoin(false);
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF);
}
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
return;
@@ -729,16 +637,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
if (params==1) {
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else {
auto rosterName= RMFT2::getRosterName(id);
if (!rosterName) rosterName=F("");
auto functionNames= RMFT2::getRosterFunctions(id);
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
if (!functionNames) functionNames=F("");
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, rosterName, functionNames);
}
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
#endif
StringFormatter::send(stream, F(">\n"));
return;
@@ -917,10 +817,7 @@ bool DCCEXParser::parseT(Print *stream, int16_t params, int16_t p[])
} else
if (params == 3 && p[1] == HASH_KEYWORD_VPIN) { // <T id VPIN n>
if (!VpinTurnout::create(p[0], p[2])) return false;
} else
if (params == 5 && p[1] == HASH_KEYWORD_HBRIDGE) { // <T id HBRIDGE pin1 pin2 delay>
if (!HBridgeTurnout::create(p[0], p[2], p[3], p[4])) return false;
} else
} else
if (params >= 3 && p[1] == HASH_KEYWORD_DCC) {
// <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>
@@ -993,9 +890,8 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
return true;
break;
#ifndef DISABLE_PROG
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
if (params >= 3) {
if (p[1] == HASH_KEYWORD_LIMIT) {
@@ -1016,7 +912,6 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
Diag::ACK = onOff;
}
return true;
#endif
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff;
@@ -1039,11 +934,11 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
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

View File

@@ -194,10 +194,8 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
setEOT(data + bitcounter++); // EOT marker
dataLen = bitcounter;
noInterrupts(); // keep dataReady and dataRepeat consistnet to each other
dataReady = true;
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
interrupts();
return 0;
}
@@ -214,8 +212,6 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() {
if (dataReady) { // if we have new data, fill while preamble is running
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
dataReady = false;
if (dataRepeat == 0) // all data should go out at least once
DIAG(F("Channel %d DCC signal lost data"), channel);
}
if (dataRepeat > 0) // if a repeat count was specified, work on that
dataRepeat--;

View File

@@ -1,7 +1,7 @@
/*
* © 2022-2023 Paul M. Antoine
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021-2023 Harald Barth
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* All rights reserved.
*
@@ -62,9 +62,6 @@ class DCCTimer {
static bool isPWMPin(byte pin);
static void setPWM(byte pin, bool high);
static void clearPWM();
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
static void DCCEXanalogWrite(uint8_t pin, int value);
// Update low ram level. Allow for extra bytes to be specified
// by estimation or inspection, that may be used by other
// called subroutines. Must be called with interrupts disabled.
@@ -105,14 +102,9 @@ private:
// that an offset can be initialized.
class ADCee {
public:
// begin is called for any setup that must be done before
// **init** can be called. On some architectures this involves ADC
// initialisation and clock routing, sampling times etc.
static void begin();
// init adds the pin to the list of scanned pins (if this
// init does add the pin to the list of scanned pins (if this
// platform's implementation scans pins) and returns the first
// read value (which is why it required begin to have been called first!)
// It must be called before the regular scan is started.
// read value. It is called before the regular scan is started.
static int init(uint8_t pin);
// read does read the pin value from the scanned cache or directly
// if this is a platform that does not scan. fromISR is a hint if
@@ -121,15 +113,19 @@ public:
static int read(uint8_t pin, bool fromISR=false);
// returns possible max value that the ADC can return
static int16_t ADCmax();
// begin is called for any setup that must be done before
// scan can be called.
static void begin();
private:
// On platforms that scan, it is called from waveform ISR
// only on a regular basis.
static void scan();
// bit array of used pins (max 16)
static uint16_t usedpins;
static uint8_t highestPin;
// cached analog values (malloc:ed to actual number of ADC channels)
static int *analogvals;
// ids to scan (new way)
static byte *idarr;
// friend so that we can call scan() and begin()
friend class DCCWaveform;
};

View File

@@ -1,6 +1,6 @@
/*
* © 2021 Mike S
* © 2021-2023 Harald Barth
* © 2021-2022 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
@@ -29,9 +29,6 @@
#include <avr/boot.h>
#include <avr/wdt.h>
#include "DCCTimer.h"
#ifdef DEBUG_ADC
#include "TrackManager.h"
#endif
INTERRUPT_CALLBACK interruptHandler=0;
// Arduino nano, uno, mega etc
@@ -131,8 +128,8 @@ void DCCTimer::reset() {
#define NUM_ADC_INPUTS 8
#endif
uint16_t ADCee::usedpins = 0;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL;
byte *ADCee::idarr = NULL;
static bool ADCusesHighPort = false;
/*
@@ -142,17 +139,28 @@ static bool ADCusesHighPort = false;
*/
int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
byte n;
if (id >= NUM_ADC_INPUTS)
return -1023;
if (id > 7)
ADCusesHighPort = true;
pinMode(pin, INPUT);
int value = analogRead(pin);
if (analogvals == NULL)
if (analogvals == NULL) {
analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int));
analogvals[id] = value;
usedpins |= (1<<id);
if (id > highestPin) highestPin = id;
for (n=0 ; n < NUM_ADC_INPUTS; n++) // set unreasonable value at startup as marker
analogvals[n] = -32768; // 16 bit int min value
idarr = (byte *)calloc(NUM_ADC_INPUTS+1, sizeof(byte)); // +1 for terminator value
for (n=0 ; n <= NUM_ADC_INPUTS; n++)
idarr[n] = 255; // set 255 as end of array marker
}
analogvals[id] = value; // store before enable by idarr[n]
for (n=0 ; n <= NUM_ADC_INPUTS; n++) {
if (idarr[n] == 255) {
idarr[n] = id;
break;
}
}
return value;
}
int16_t ADCee::ADCmax() {
@@ -162,14 +170,14 @@ int16_t ADCee::ADCmax() {
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
(void)fromISR; // AVR does ignore this arg
uint8_t id = pin - A0;
if ((usedpins & (1<<id) ) == 0)
return -1023;
int a;
// we do not need to check (analogvals == NULL)
// because usedpins would still be 0 in that case
if (!fromISR) noInterrupts();
int a = analogvals[id];
if (!fromISR) interrupts();
noInterrupts();
a = analogvals[id];
interrupts();
return a;
}
/*
@@ -178,8 +186,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static byte id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static byte num = 0; // index into id array
static bool waiting = false;
if (waiting) {
@@ -191,49 +198,26 @@ void ADCee::scan() {
low = ADCL; //must read low before high
high = ADCH;
bitSet(ADCSRA, ADIF);
analogvals[id] = (high << 8) | low;
// advance at least one track
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
#endif
analogvals[idarr[num]] = (high << 8) | low;
waiting = false;
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
}
if (!waiting) {
if (usedpins == 0) // otherwise we would loop forever
return;
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
// cycle around in-use analogue pins
num++;
if (idarr[num] == 255)
num = 0;
// start new ADC aquire on id
#if defined(ADCSRB) && defined(MUX5)
if (ADCusesHighPort) { // if we ever have started to use high pins)
if (id > 7) // if we use a high ADC pin
bitSet(ADCSRB, MUX5); // set MUX5 bit
else
bitClear(ADCSRB, MUX5);
}
#endif
ADMUX=(1<<REFS0)|(id & 0x07); //select AVCC as reference and set MUX
bitSet(ADCSRA,ADSC); // start conversion
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
}
if (ADCusesHighPort) { // if we ever have started to use high pins)
if (idarr[num] > 7) // if we use a high ADC pin
bitSet(ADCSRB, MUX5); // set MUX5 bit
else
bitClear(ADCSRB, MUX5);
}
#endif
ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX
bitSet(ADCSRA, ADSC); // start conversion
waiting = true;
}
}
#pragma GCC pop_options
@@ -247,4 +231,4 @@ void ADCee::begin() {
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
interrupts();
}
#endif
#endif

View File

@@ -150,45 +150,6 @@ int DCCTimer::freeMemory() {
void DCCTimer::reset() {
ESP.restart();
}
#include "esp32-hal.h"
#include "soc/soc_caps.h"
#ifdef SOC_LEDC_SUPPORT_HS_MODE
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
#else
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
#endif
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
static int cnt_channel = LEDC_CHANNELS;
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] != 0) {
ledcSetup(pin_to_channel[pin], frequency, 8);
}
}
}
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] == 0) {
if (!cnt_channel) {
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcSetup(cnt_channel, 1000, 8);
ledcAttachPin(pin, cnt_channel);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
}
ledcWrite(pin_to_channel[pin], value);
}
}
int ADCee::init(uint8_t pin) {
pinMode(pin, ANALOG);
adc1_config_width(ADC_WIDTH_BIT_12);

View File

@@ -162,7 +162,7 @@ uint16_t ADCee::usedpins = 0;
int * ADCee::analogvals = NULL;
int ADCee::init(uint8_t pin) {
uint8_t id = pin - A0;
uint id = pin - A0;
int value = 0;
if (id > NUM_ADC_INPUTS)
@@ -210,7 +210,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static uint8_t id = 0; // id and mask are the same thing but it is faster to
static uint id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;

View File

@@ -1,8 +1,8 @@
/*
* © 2023 Neil McKechnie
* © 2022-23 Paul M. Antoine
* © 2022 Paul M. Antoine
* © 2021 Mike S
* © 2021, 2023 Harald Barth
* © 2021 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
@@ -30,35 +30,24 @@
#ifdef ARDUINO_ARCH_STM32
#include "DCCTimer.h"
#ifdef DEBUG_ADC
#include "TrackManager.h"
#endif
#include "DIAG.h"
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have additional serial ports defined by default
#if defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
#elif defined(ARDUINO_NUCLEO_F446RE)
// Nucleo-64 boards don't have additional serial ports defined by default
// On the F446RE, Serial1 isn't really useable as it's Rx/Tx pair sit on already used D2/D10 pins
// HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
// Nucleo-64 boards don't have Serial1 defined by default
HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
// On the F446RE, Serial3 and Serial5 are easy to use:
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG)
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
#else
#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
#warning Serial1 not defined
#endif
///////////////////////////////////////////////////////////////////////////////////////////////
@@ -235,16 +224,10 @@ void DCCTimer::reset() {
while(true) {};
}
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
#endif
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
#define NUM_ADC_INPUTS 16
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
uint16_t ADCee::usedpins = 0;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL;
uint32_t * analogchans = NULL;
bool adc1configured = false;
@@ -254,13 +237,10 @@ int16_t ADCee::ADCmax() {
}
int ADCee::init(uint8_t pin) {
uint id = pin - A0;
int value = 0;
PinName stmpin = analogInputToPinName(pin);
if (stmpin == NC) // do not continue if this is not an analog pin at all
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)
PinName stmpin = digitalPin[analogInputPin[id]];
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
GPIO_TypeDef * gpioBase;
@@ -278,20 +258,12 @@ int ADCee::init(uint8_t pin) {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
gpioBase = GPIOC;
break;
default:
return -1023; // some silly value as error
}
// Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin
gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11)
// Set pin mux mode to analog input
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
// Set the sampling rate for that analog input
// This is F411x specific! Different on for example F334
// STM32F11xC/E Reference manual
// 11.12.4 ADC sample time register 1 (ADC_SMPR1) (channels 10 to 18)
// 11.12.5 ADC sample time register 2 (ADC_SMPR2) (channels 0 to 9)
if (adcchan > 18)
return -1022; // silly value as error
if (adcchan < 10)
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
else
@@ -303,21 +275,14 @@ int ADCee::init(uint8_t pin) {
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
value = ADC1->DR; // Read value from register
uint8_t id = pin - PNUM_ANALOG_BASE;
if (id > 15) { // today we have not enough bits in the mask to support more
return -1021;
}
if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init.
if (analogvals == NULL)
{
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
}
analogvals[id] = value; // Store sampled value
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
usedpins |= (1 << id); // This pin is now ready
if (id > highestPin) highestPin = id; // Store our highest pin in use
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
return value;
}
@@ -326,7 +291,7 @@ int ADCee::init(uint8_t pin) {
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
uint8_t id = pin - PNUM_ANALOG_BASE;
uint8_t id = pin - A0;
// Was this pin initialised yet?
if ((usedpins & (1<<id) ) == 0)
return -1023;
@@ -341,7 +306,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
static uint8_t id = 0; // id and mask are the same thing but it is faster to
static uint id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;
@@ -352,13 +317,11 @@ void ADCee::scan() {
// found value
analogvals[id] = ADC1->DR;
// advance at least one track
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
#endif
// for scope debug TrackManager::track[1]->setBrake(0);
waiting = false;
id++;
mask = mask << 1;
if (id > highestPin) { // the 1 has been shifted out
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
@@ -369,20 +332,18 @@ void ADCee::scan() {
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
// start new ADC aquire on id
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
waiting = true;
return;
// for scope debug TrackManager::track[1]->setBrake(1);
waiting = true;
return;
}
id++;
mask = mask << 1;
if (id > highestPin) {
id = 0;
mask = 1;
if (id == NUM_ADC_INPUTS+1) {
id = 0;
mask = 1;
}
}
}
@@ -407,4 +368,4 @@ void ADCee::begin() {
ADC1->CR2 |= (1 << 0); // Switch on ADC1
interrupts();
}
#endif
#endif

View File

@@ -247,9 +247,6 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
pendingPacket[byteCount] = checksum;
pendingLength = byteCount + 1;
pendingRepeats = repeats;
// DIAG repeated commands (accesories)
// if (pendingRepeats > 0)
// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG");
// The resets will be zero not only now but as well repeats packets into the future
clearResets(repeats+1);
{

61
ESP32-fixes.cpp Normal file
View File

@@ -0,0 +1,61 @@
/*
* © 2022 Harald Barth
* 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/>.
*/
#ifdef ARDUINO_ARCH_ESP32
#include <Arduino.h>
#include "ESP32-fixes.h"
#include "esp32-hal.h"
#include "soc/soc_caps.h"
#ifdef SOC_LEDC_SUPPORT_HS_MODE
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
#else
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
#endif
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
static int cnt_channel = LEDC_CHANNELS;
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] != 0) {
ledcSetup(pin_to_channel[pin], frequency, 8);
}
}
}
void DCCEXanalogWrite(uint8_t pin, int value) {
if (pin < SOC_GPIO_PIN_COUNT) {
if (pin_to_channel[pin] == 0) {
if (!cnt_channel) {
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcAttachPin(pin, cnt_channel);
ledcSetup(cnt_channel, 1000, 8);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
}
ledcWrite(pin_to_channel[pin], value);
}
}
#endif

26
ESP32-fixes.h Normal file
View File

@@ -0,0 +1,26 @@
/*
* © 2022 Harald Barth
* 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/>.
*/
#ifdef ARDUINO_ARCH_ESP32
#pragma once
#include <Arduino.h>
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
void DCCEXanalogWrite(uint8_t pin, int value);
#endif

Binary file not shown.

View File

@@ -2,7 +2,6 @@
* © 2021 Neil McKechnie
* © 2021-2023 Harald Barth
* © 2020-2023 Chris Harlow
* © 2022 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -238,16 +237,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
case OPCODE_HBRIDGETURNOUT: {
VPIN id=operand;
VPIN pin1=getOperand(progCounter, 1);
VPIN pin2=getOperand(progCounter, 2);
uint16_t delay=getOperand(progCounter, 3);
setTurnoutHiddenState(HBridgeTurnout::create(id,pin1, pin2, delay));
break;
}
case OPCODE_AUTOSTART:
// automatically create a task from here at startup.
// Removed if (progCounter>0) check 4.2.31 because
@@ -275,17 +265,16 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) {
char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i);
if (rid==INT16_MAX) break;
if (rid==id) return 'R';
if (rid==0) break;
}
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(automationIdList,i);
if (rid==INT16_MAX) break;
if (rid==id) return 'A';
if (rid==0) break;
}
return 'X';
}
}
// This filter intercepts <> commands to do the following:
// - Implement RMFT specific commands/diagnostics
// - Reject/modify JMRI commands that would interfere with RMFT processing
@@ -619,7 +608,6 @@ void RMFT2::loop2() {
break;
case OPCODE_SPEED:
forward=DCC::getThrottleDirection(loco)^invert;
driveLoco(operand);
break;
@@ -714,11 +702,11 @@ void RMFT2::loop2() {
DCC::setThrottle(0,1,true); // pause all locos on the track
pausingTask=this;
break;
case OPCODE_POM:
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
break;
case OPCODE_POWEROFF:
TrackManager::setPower(POWERMODE::OFF);
TrackManager::setJoin(false);
@@ -893,18 +881,23 @@ void RMFT2::loop2() {
while(loopTask) loopTask->kill(F("KILLALL"));
return;
#ifndef DISABLE_PROG
case OPCODE_JOIN:
TrackManager::setPower(POWERMODE::ON);
TrackManager::setJoin(true);
CommandDistributor::broadcastPower();
break;
case OPCODE_POWERON:
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_UNJOIN:
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet
DCC::getLocoId(readLocoCallback);
@@ -925,13 +918,6 @@ void RMFT2::loop2() {
forward=true;
invert=false;
break;
#endif
case OPCODE_POWERON:
TrackManager::setMainPower(POWERMODE::ON);
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_START:
{
@@ -1143,10 +1129,7 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
// Hunt for an ONTIME for this time
if (Diag::CMD)
DIAG(F("Looking for clock event at : %d"), clocktime);
if (change) {
handleEvent(F("CLOCK"),onClockLookup,clocktime);
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
}
if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime);
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
@@ -1254,10 +1237,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL);
break;
case thrunge_broadcast:
CommandDistributor::broadcastRaw(CommandDistributor::COMMAND_TYPE,buffer->getString());
break;
case thrunge_withrottle:
CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString());
// TODO CommandDistributor::broadcastText(buffer->getString());
break;
case thrunge_lcd:
LCD(id,F("%s"),buffer->getString());

View File

@@ -1,7 +1,6 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2022 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved.
*
@@ -45,14 +44,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
#ifndef DISABLE_PROG
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
#endif
OPCODE_POM,
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT,
OPCODE_PINTURNOUT, OPCODE_HBRIDGETURNOUT,
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
OPCODE_PRINT,OPCODE_DCCACTIVATE,
OPCODE_ONACTIVATE,OPCODE_ONDEACTIVATE,
OPCODE_ROSTER,OPCODE_KILLALL,
@@ -81,8 +76,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
// Ensure thrunge_lcd is put last as there may be more than one display,
// sequentially numbered from thrunge_lcd.
enum thrunger: byte {
thrunge_print, thrunge_broadcast, thrunge_withrottle,
thrunge_serial,thrunge_parse,
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
thrunge_serial1, thrunge_serial2, thrunge_serial3,
thrunge_serial4, thrunge_serial5, thrunge_serial6,
thrunge_lcn,

View File

@@ -1,6 +1,5 @@
/*
* © 2020-2022 Chris Harlow. All rights reserved.
* © 2022 Colin Murdoch
* © 2023 Harald Barth
*
* This file is part of CommandStation-EX
@@ -62,7 +61,6 @@
#undef FWD
#undef GREEN
#undef HAL
#undef HBRIDGE_TURNOUT
#undef IF
#undef IFAMBER
#undef IFCLOSED
@@ -93,7 +91,6 @@
#undef ONCLOSE
#undef ONTIME
#undef ONCLOCKTIME
#undef ONCLOCKMINS
#undef ONGREEN
#undef ONRED
#undef ONTHROW
@@ -102,9 +99,7 @@
#undef PAUSE
#undef PIN_TURNOUT
#undef PRINT
#ifndef DISABLE_PROG
#undef POM
#endif
#undef POWEROFF
#undef POWERON
#undef READ_LOCO
@@ -139,13 +134,11 @@
#undef STOP
#undef THROW
#undef TURNOUT
#undef TURNOUTL
#undef UNJOIN
#undef UNLATCH
#undef VIRTUAL_SIGNAL
#undef VIRTUAL_TURNOUT
#undef WAITFOR
#undef WITHROTTLE
#undef XFOFF
#undef XFON
@@ -188,7 +181,6 @@
#define FWD(speed)
#define GREEN(signal_id)
#define HAL(haltype,params...)
#define HBRIDGE_TURNOUT(id,pin1,pin2,dly,description...)
#define IF(sensor_id)
#define IFAMBER(signal_id)
#define IFCLOSED(turnout_id)
@@ -216,7 +208,6 @@
#define ONAMBER(signal_id)
#define ONTIME(value)
#define ONCLOCKTIME(hours,mins)
#define ONCLOCKMINS(mins)
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
@@ -228,9 +219,7 @@
#define PIN_TURNOUT(id,pin,description...)
#define PRINT(msg)
#define PARSE(msg)
#ifndef DISABLE_PROG
#define POM(cv,value)
#endif
#define POWEROFF
#define POWERON
#define READ_LOCO
@@ -265,13 +254,11 @@
#define STOP
#define THROW(id)
#define TURNOUT(id,addr,subaddr,description...)
#define TURNOUTL(id,addr,description...)
#define UNJOIN
#define UNLATCH(sensor_id)
#define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...)
#define WAITFOR(pin)
#define WITHROTTLE(msg)
#define XFOFF(cab,func)
#define XFON(cab,func)
#endif

View File

@@ -1,7 +1,6 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2022 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved.
*
@@ -81,14 +80,14 @@ void exrailHalSetup() {
#define ROUTE(id, description) id,
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
#include "myAutomation.h"
INT16_MAX};
0};
// Pass 2a create throttle automation list
#include "EXRAIL2MacroReset.h"
#undef AUTOMATION
#define AUTOMATION(id, description) id,
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
#include "myAutomation.h"
INT16_MAX};
0};
// Pass 3 Create route descriptions:
#undef ROUTE
@@ -153,8 +152,6 @@ const int StringMacroTracker1=__COUNTER__;
lcdid=id;\
break;\
}
#undef WITHROTTLE
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
void RMFT2::printMessage(uint16_t id) {
thrunger tmode;
@@ -172,10 +169,6 @@ void RMFT2::printMessage(uint16_t id) {
#include "EXRAIL2MacroReset.h"
#undef TURNOUT
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
#undef TURNOUTL
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
#undef HBRIDGE_TURNOUT
#define HBRIDGE_TURNOUT(id,pin1,pin2,delay_ms,description...) O_DESC(id,description)
#undef PIN_TURNOUT
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
#undef SERVO_TURNOUT
@@ -194,7 +187,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
// Pass 6: Roster IDs (count)
#include "EXRAIL2MacroReset.h"
#undef ROSTER
#define ROSTER(cabid,name,funcmap...) +(cabid <= 0 ? 0 : 1)
#define ROSTER(cabid,name,funcmap...) +1
const byte RMFT2::rosterNameCount=0
#include "myAutomation.h"
;
@@ -205,7 +198,7 @@ const byte RMFT2::rosterNameCount=0
#define ROSTER(cabid,name,funcmap...) cabid,
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
#include "myAutomation.h"
INT16_MAX};
0};
// Pass 7: Roster names getter
#include "EXRAIL2MacroReset.h"
@@ -227,7 +220,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
#include "myAutomation.h"
default: break;
}
return NULL;
return F("");
}
// Pass 8 Signal definitions
@@ -295,7 +288,6 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define FWD(speed) OPCODE_FWD,V(speed),
#define GREEN(signal_id) OPCODE_GREEN,V(signal_id),
#define HAL(haltype,params...)
#define HBRIDGE_TURNOUT(id,pin1,pin2,delay,description...) OPCODE_HBRIDGETURNOUT,V(id),OPCODE_PAD,V(pin1),OPCODE_PAD,V(pin2),OPCODE_PAD,V(delay),
#define IF(sensor_id) OPCODE_IF,V(sensor_id),
#define IFAMBER(signal_id) OPCODE_IFAMBER,V(signal_id),
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
@@ -324,7 +316,6 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
@@ -332,10 +323,8 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#ifndef DISABLE_PROG
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
#endif
#define POWEROFF OPCODE_POWEROFF,0,0,
#define POWERON OPCODE_POWERON,0,0,
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
@@ -372,12 +361,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id),
#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 UNJOIN OPCODE_UNJOIN,0,0,
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
#define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
#define WITHROTTLE(msg) PRINT(msg)
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),

View File

@@ -1 +1 @@
#define GITHUB_SHA "3bddf4d"
#define GITHUB_SHA "devel-202303252126Z"

View File

@@ -63,31 +63,15 @@ void IODevice::begin() {
if (exrailHalSetup)
exrailHalSetup();
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
// Predefine two PCA9685 modules 0x40-0x41
// Allocates 32 pins 100-131
if (checkNoOverlap(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)) {
PCA9685::create(116, 16, 0x41);
} else {
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
}
PCA9685::create(100, 16, 0x40);
PCA9685::create(116, 16, 0x41);
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
// Predefine two MCP23017 module 0x20/0x21
// Allocates 32 pins 164-195
if (checkNoOverlap(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)) {
MCP23017::create(180, 16, 0x21);
} else {
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
}
MCP23017::create(164, 16, 0x20);
MCP23017::create(180, 16, 0x21);
}
// reset() function to reinitialise all devices

View File

@@ -277,13 +277,23 @@ private:
class PCA9685 : public IODevice {
public:
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50);
#define NUMUSERPROFILES 8
enum ProfileType : uint8_t {
Instant = 0, // Moves immediately between positions (if duration not specified)
UseDuration = 0, // Use specified duration
Fast = 1, // Takes around 500ms end-to-end
Medium = 2, // 1 second end-to-end
Slow = 3, // 2 seconds end-to-end
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
UserProfile0 = 4, // For user-defined profiles
UserProfile1 = 5,
UserProfile2 = 6,
UserProfile3 = 7,
UserProfile4 = 8,
UserProfile5 = 9,
UserProfile6 = 10,
UserProfile7 = 11,
LastUserProfile = 11,
Bounce = UserProfile0, // For semaphores/turnouts with a bit of bounce!!
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
};
@@ -319,7 +329,8 @@ private:
struct ServoData *_servoData [16];
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
static const uint8_t FLASH _bounceProfile[30];
static const FLASH uint8_t _bounceProfile[];
static const uint8_t *_profiles[];
const unsigned int refreshInterval = 50; // refresh every 50ms

View File

@@ -50,12 +50,12 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000);
if (I2CManager.exists(_I2CAddress)) {
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}

View File

@@ -1,112 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef io_pca9555_h
#define io_pca9555_h
#include "IO_GPIOBase.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for PCA9555 16-bit I/O expander (NXP & Texas Instruments).
*/
class PCA9555 : public GPIOBase<uint16_t> {
public:
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
new PCA9555(vpin, min(nPins,16), I2CAddress, interruptPin);
}
// Constructor
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
{
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_INPUT_P0;
}
private:
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
}
void _writePullups() override {
// Do nothing, pull-ups are always in place for input ports
// This function is here for HAL GPIOBase API compatibilitiy
}
void _writePortModes() override {
// Write 0 to REG_CONF_P0 & REG_CONF_P1 for in-use pins that are outputs, 1 for others.
// PCA9555 & TCA9555, Interrupt is always enabled for raising and falling edge
uint16_t temp = ~(_portMode & _portInUse);
I2CManager.write(_I2CAddress, 3, REG_CONF_P0, temp, temp>>8);
}
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer[2];
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_INPUT_P0);
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0];
/* PCA9555 Int bug fix, from PCA9555 datasheet: "must change command byte to something besides 00h
* after a Read operation to the PCA9555 device or before reading from
* another device"
* Recommended solution, read from REG_OUTPUT_P0, then do nothing with the received data
* Issue not seen during testing, uncomment if needed
*/
//I2CManager.read(_I2CAddress, buffer, 2, 1, REG_OUTPUT_P0);
} else {
// Queue new request
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0];
else
_portInputState = 0xffff;
}
void _setupDevice() override {
// HAL API calls
_writePortModes();
_writePullups();
_writeGpioPort();
}
uint8_t inputBuffer[2];
uint8_t outputBuffer[1];
enum {
REG_INPUT_P0 = 0x00,
REG_INPUT_P1 = 0x01,
REG_OUTPUT_P0 = 0x02,
REG_OUTPUT_P1 = 0x03,
REG_POL_INV_P0 = 0x04,
REG_POL_INV_P1 = 0x05,
REG_CONF_P0 = 0x06,
REG_CONF_P1 = 0x07,
};
};
#endif

View File

@@ -141,7 +141,7 @@ void PCA9685::_write(VPIN vpin, int value) {
// 1 (Fast) Move servo in 0.5 seconds
// 2 (Medium) Move servo in 1.0 seconds
// 3 (Slow) Move servo in 2.0 seconds
// 4 (Bounce) Servo 'bounces' at extremes.
// >=4 Predefined profiles: Bounce, UserProfile1, UserProfile2 etc.
//
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
#ifdef DIAG_IO
@@ -165,13 +165,22 @@ void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t dur
}
// Animated profile. Initiate the appropriate action.
s->currentProfile = profile;
uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit.
s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds
profileValue==Medium ? 20 : // 1.0 seconds
profileValue==Slow ? 40 : // 2.0 seconds
profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds
duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms)
if (profileValue >= UserProfile0 && profileValue <= LastUserProfile) {
const uint8_t *ptr = _profiles[profileValue - UserProfile0];
if (ptr != NULL) {
s->numSteps = GETFLASH(ptr); // First entry in array is number of steps (1-255)
} else {
profileValue = 0; // Profile not assigned, so use instant.
s->numSteps = 1;
}
} else {
s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds
profileValue==Medium ? 20 : // 1.0 seconds
profileValue==Slow ? 40 : // 2.0 seconds
duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms)
}
s->currentProfile = (profile & NoPowerOff) | profileValue; // Adjust profile if necessary
s->stepNumber = 0;
s->toPosition = value;
s->fromPosition = s->currentPosition;
@@ -213,9 +222,10 @@ void PCA9685::updatePosition(uint8_t pin) {
if (s->stepNumber < s->numSteps) {
// Animation in progress, reposition servo
s->stepNumber++;
if ((s->currentProfile & ~NoPowerOff) == Bounce) {
// Retrieve step positions from array in flash
byte profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
uint8_t profile = s->currentProfile & ~NoPowerOff;
if (profile >= UserProfile0 && profile <= LastUserProfile) {
const uint8_t *ptr = _profiles[profile-UserProfile0];
byte profileValue = GETFLASH(ptr + s->stepNumber);
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
} else {
// All other profiles - calculate step by linear interpolation between from and to positions.
@@ -274,6 +284,16 @@ static void writeRegister(byte address, byte reg, byte value) {
// Profile for a bouncing signal or turnout
// The profile below is in the range 0-100% and should be combined with the desired limits
// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here,
// i.e. the bounce is the same on the down action as on the up action. First entry isn't used.
const uint8_t FLASH PCA9685::_bounceProfile[30] =
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};
// i.e. the bounce is the same on the down action as on the up action. First entry is the number of steps.
const FLASH uint8_t PCA9685::_bounceProfile[] =
{30, 0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};
extern __attribute__((weak)) const FLASH uint8_t _UserProfile1[];
extern __attribute__((weak)) const FLASH uint8_t _UserProfile2[];
extern __attribute__((weak)) const FLASH uint8_t _UserProfile3[];
extern __attribute__((weak)) const FLASH uint8_t _UserProfile4[];
extern __attribute__((weak)) const FLASH uint8_t _UserProfile5[];
extern __attribute__((weak)) const FLASH uint8_t _UserProfile6[];
extern __attribute__((weak)) const FLASH uint8_t _UserProfile7[];
const uint8_t *PCA9685::_profiles[] = {_bounceProfile,
_UserProfile1, _UserProfile2, _UserProfile3, _UserProfile4, _UserProfile5, _UserProfile6, _UserProfile7};

View File

@@ -1,5 +1,4 @@
/*
* © 2023, Peter Cole. All rights reserved.
* © 2022, Peter Cole. All rights reserved.
*
* This file is part of EX-CommandStation
@@ -29,23 +28,9 @@
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
*
* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
*
* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other
* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished
* using the EXRAIL SERVO(vpin, position, profile) command, where:
* - vpin = the third defined Vpin (any other is ignored)
* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255
* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance
*
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
*
* #include "IO_RotaryEncoder.h"
* HAL(RotaryEncoder, 700, 1, 0x70) // 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, 3, 0x70) // 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.
*/
@@ -59,88 +44,58 @@
class RotaryEncoder : public IODevice {
public:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = i2cAddress;
addDevice(this);
}
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress);
}
private:
// Constructor
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
_firstVpin = firstVpin;
_nPins = nPins;
if (_nPins > 3) {
_nPins = 3;
DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins);
}
_I2CAddress = i2cAddress;
addDevice(this);
}
// Initiate the device
void _begin() {
uint8_t _status;
// Attempt to initilalise device
I2CManager.begin();
if (I2CManager.exists(_I2CAddress)) {
// Send RE_RDY, must receive RE_RDY to be online
_sendBuffer[0] = RE_RDY;
_status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1);
if (_status == I2C_STATUS_OK) {
if (_rcvBuffer[0] == RE_RDY) {
_sendBuffer[0] = RE_VER;
if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) {
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
}
} else {
DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]);
_deviceState = DEVSTATE_FAILED;
return;
}
} else {
DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
return;
}
byte _getVersion[1] = {RE_VER};
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
_majorVer = _versionBuffer[0];
_minorVer = _versionBuffer[1];
_patchVer = _versionBuffer[2];
_buffer[0] = RE_OP;
I2CManager.write(_I2CAddress, _buffer, 1);
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
_deviceState = DEVSTATE_FAILED;
}
}
void _loop(unsigned long currentMicros) override {
if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed
if (_i2crb.isBusy()) return; // Return if I2C operation still in progress
if (currentMicros - _lastPositionRead > _positionRefresh) {
_lastPositionRead = currentMicros;
_sendBuffer[0] = RE_READ;
I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder
_position = _rcvBuffer[0];
// If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use
#if defined(EXRAIL_ACTIVE)
I2CManager.read(_I2CAddress, _buffer, 1);
_position = _buffer[0];
// This here needs to have a change check, ie. position is a different value.
#if defined(EXRAIL_ACTIVE)
if (_position != _previousPosition) {
_previousPosition = _position;
RMFT2::changeEvent(_firstVpin, 1);
RMFT2::changeEvent(_firstVpin,1);
} else {
RMFT2::changeEvent(_firstVpin, 0);
RMFT2::changeEvent(_firstVpin,0);
}
#endif
}
#endif
delayUntil(currentMicros + 100000);
}
// Return the position sent by the rotary encoder software
// Device specific read function
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return _position;
}
// Send the feedback value to the rotary encoder software
void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) {
if (value != 0) value = 0x01;
@@ -148,19 +103,6 @@ private:
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
}
}
// Send a position update to the rotary encoder software
// To be valid, must be 0 to 255, and different to the current position
// If the current position is the same, it was initiated by the rotary encoder
void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override {
if (vpin == _firstVpin + 2) {
if (position >= 0 && position <= 255 && position != _position) {
byte newPosition = position & 0xFF;
byte _positionBuffer[2] = {RE_MOVE, newPosition};
I2CManager.write(_I2CAddress, _positionBuffer, 2);
}
}
}
void _display() override {
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
@@ -170,21 +112,14 @@ private:
int8_t _position;
int8_t _previousPosition = 0;
uint8_t _versionBuffer[3];
uint8_t _sendBuffer[1];
uint8_t _rcvBuffer[1];
uint8_t _buffer[1];
uint8_t _majorVer = 0;
uint8_t _minorVer = 0;
uint8_t _patchVer = 0;
I2CRB _i2crb;
unsigned long _lastPositionRead = 0;
const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms
enum {
RE_RDY = 0xA0, // Flag to check if encoder is ready for operation
RE_VER = 0xA1, // Flag to retrieve rotary encoder software version
RE_READ = 0xA2, // Flag to read the current position of the encoder
RE_OP = 0xA3, // Flag for operation start/end, sent to when sending feedback on move start/end
RE_MOVE = 0xA4, // Flag for sending a position update from the device driver to the encoder
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
RE_OP = 0xA1, // Flag for normal operation
};
};

View File

@@ -1,115 +0,0 @@
/*
* © 2023, Sergei Kotlyachkov. 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_SCHEDULED_PIN_H
#define IO_SCHEDULED_PIN_H
#include "IODevice.h"
#include <Arduino.h>
#include "defines.h"
/**
* Bounces back single Arduino Pin to specified state after set period of time.
*
* It will establish itself as owner of the pin over ArduinoPins class that typically responds to it and
* activates itself during loop() phase. It restores scheduled state and does not try again until
* another write()
*
* Example usage:
* Create: ScheduledPin::create(5, LOW, 20000);
*
* Then, when neeeded, just call:
* IODevice::write(5, HIGH); // this will call fastWriteDigital(5, HIGH)
*
* In 20 milliseconds, it will also call fastWriteDigital(5, LOW)
*
* In edge case where write() is called twice before responding in the loop,
* the schedule will restart and double the bounce back time.
*/
class ScheduledPin : public IODevice {
private:
int _scheduledValue;
uint32_t _durationMicros;
public:
// Static function to handle create calls.
static void create(VPIN pin, int scheduledValue, uint32_t durationMicros) {
new ScheduledPin(pin, scheduledValue, durationMicros);
}
protected:
// Constructor.
ScheduledPin(VPIN pin, int scheduledValue, uint32_t durationMicros) : IODevice(pin, 1) {
_scheduledValue = scheduledValue;
_durationMicros = durationMicros;
// Typically returned device will be ArduinoPins
IODevice* controlledDevice = IODevice::findDevice(pin);
if (controlledDevice != NULL) {
addDevice(this, controlledDevice);
}
else {
DIAG(F("ScheduledPin Controlled device not found for VPIN:%d"), pin);
_deviceState = DEVSTATE_FAILED;
}
}
// Device-specific initialisation
void _begin() override {
#ifdef DIAG_IO
_display();
#endif
pinMode(_firstVpin, OUTPUT);
ArduinoPins::fastWriteDigital(_firstVpin, _scheduledValue);
}
void _write(VPIN vpin, int value) override {
if (_deviceState == DEVSTATE_FAILED) return;
if (vpin != _firstVpin) {
#ifdef DIAG_IO
DIAG(F("ScheduledPin Error VPIN:%u not equal to %u"), vpin, _firstVpin);
#endif
return;
}
#ifdef DIAG_IO
DIAG(F("ScheduledPin Write VPIN:%u Value:%d Micros:%l"), vpin, value, micros());
#endif
unsigned long currentMicros = micros();
delayUntil(currentMicros + _durationMicros);
ArduinoPins::fastWriteDigital(_firstVpin, value);
}
void _loop(unsigned long currentMicros) {
if (_deviceState == DEVSTATE_FAILED) return;
#ifdef DIAG_IO
DIAG(F("ScheduledPin Bounce VPIN:%u Value:%d Micros:%l"), _firstVpin, _scheduledValue, micros());
#endif
ArduinoPins::fastWriteDigital(_firstVpin, _scheduledValue);
delayUntil(currentMicros + 0x7fffffff); // Largest time in the future! Effectively disable _loop calls.
}
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
void _display() {
DIAG(F("ScheduledPin Configured:%u Value:%d Duration:%l"), (int)_firstVpin,
_scheduledValue, _durationMicros);
}
};
#endif // IO_SCHEDULED_PIN_H

View File

@@ -55,7 +55,6 @@ public:
pinMode(_clockPin,OUTPUT);
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
_display();
if (!_pinMap) _loopOutput();
}
// loop called by HAL supervisor

View File

@@ -1,8 +1,8 @@
/*
* © 2022-2023 Paul M Antoine
* © 2022 Paul M Antoine
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020-2023 Harald Barth
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* All rights reserved.
*
@@ -27,16 +27,19 @@
#include "DCCTimer.h"
#include "DIAG.h"
unsigned long MotorDriver::globalOverloadStart = 0;
#if defined(ARDUINO_ARCH_ESP32)
#include "ESP32-fixes.h"
#endif
bool MotorDriver::commonFaultPin=false;
volatile portreg_t shadowPORTA;
volatile portreg_t shadowPORTB;
volatile portreg_t shadowPORTC;
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) {
const FSH * warnString = F("** WARNING **");
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
powerPin=power_pin;
invertPower=power_pin < 0;
if (invertPower) {
powerPin = 0-power_pin;
@@ -92,54 +95,32 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
}
else dualSignal=false;
brakePin=brake_pin;
if (brake_pin!=UNUSED_PIN){
invertBrake=brake_pin < 0;
if (invertBrake)
brake_pin = 0-brake_pin;
if (brake_pin > MAX_PIN)
DIAG(F("%S Brake pin %d > %d"), warnString, brake_pin, MAX_PIN);
brakePin=(byte)brake_pin;
brakePin=invertBrake ? 0-brake_pin : brake_pin;
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
// if brake is used for railcom cutout we need to do PORTX register trick here as well
pinMode(brakePin, OUTPUT);
setBrake(true); // start with brake on in case we hace DC stuff going on
} else {
brakePin=UNUSED_PIN;
}
else brakePin=UNUSED_PIN;
currentPin=current_pin;
if (currentPin!=UNUSED_PIN) {
int ret = ADCee::init(currentPin);
if (ret < -1010) { // XXX give value a name later
DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin);
currentPin = UNUSED_PIN;
}
}
if (currentPin!=UNUSED_PIN) ADCee::init(currentPin);
senseOffset=0; // value can not be obtained until waveform is activated
if (fault_pin != UNUSED_PIN) {
invertFault=fault_pin < 0;
if (invertFault)
fault_pin = 0-fault_pin;
if (fault_pin > MAX_PIN)
DIAG(F("%S Fault pin %d > %d"), warnString, fault_pin, MAX_PIN);
faultPin=(byte)fault_pin;
DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault);
faultPin=fault_pin;
if (faultPin != UNUSED_PIN) {
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
pinMode(faultPin, INPUT);
} else {
faultPin=UNUSED_PIN;
}
// This conversion performed at compile time so the remainder of the code never needs
// float calculations or libraray code.
senseFactorInternal=sense_factor * senseScale;
tripMilliamps=trip_milliamps;
#ifdef MAX_CURRENT
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
tripMilliamps = MAX_CURRENT;
#endif
rawCurrentTripValue=mA2raw(tripMilliamps);
rawCurrentTripValue=mA2raw(trip_milliamps);
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never
@@ -154,16 +135,20 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
}
if (currentPin==UNUSED_PIN)
DIAG(F("%S No current or short detection"), warnString);
DIAG(F("** WARNING ** No current or short detection"));
else {
DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue);
DIAG(F("Track %c, TripValue=%d"), trackLetter, rawCurrentTripValue);
// self testing diagnostic for the non-float converters... may be removed when happy
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
}
// prepare values for current detection
sampleDelay = 0;
lastSampleTaken = millis();
progTripValue = mA2raw(TRIP_CURRENT_PROG);
}
bool MotorDriver::isPWMCapable() {
@@ -172,12 +157,7 @@ bool MotorDriver::isPWMCapable() {
void MotorDriver::setPower(POWERMODE mode) {
if (powerMode == mode) return;
//DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode);
lastPowerChange[(int)mode] = micros();
if (mode == POWERMODE::OVERLOAD)
globalOverloadStart = lastPowerChange[(int)mode];
bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT);
bool on=mode==POWERMODE::ON;
if (on) {
// when switching a track On, we need to check the crrentOffset with the pin OFF
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
@@ -217,8 +197,8 @@ bool MotorDriver::canMeasureCurrent() {
return currentPin!=UNUSED_PIN;
}
/*
* Return the current reading as pin reading 0 to max resolution (1024 or 4096).
* If the fault pin is activated return a negative current to show active fault pin.
* Return the current reading as pin reading 0 to 1023. If the fault
* pin is activated return a negative current to show active fault pin.
* As there is no -0, cheat a little and return -1 in that case.
*
* senseOffset handles the case where a shield returns values above or below
@@ -230,17 +210,12 @@ int MotorDriver::getCurrentRaw(bool fromISR) {
(void)fromISR;
if (currentPin==UNUSED_PIN) return 0;
int current;
current = ADCee::read(currentPin, fromISR);
// here one can diag raw value
// if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
current = current-senseOffset; // adjust with offset
current = ADCee::read(currentPin, fromISR)-senseOffset;
if (current<0) current=0-current;
// current >= 0 here, we use negative current as fault pin flag
if ((faultPin != UNUSED_PIN) && powerPin) {
if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin))
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
return (current == 0 ? -1 : -current);
}
return current;
}
#ifdef ANALOG_READ_INTERRUPT
@@ -278,7 +253,6 @@ void MotorDriver::startCurrentFromHW() {
#endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32)
#ifdef VARIABLE_TONES
uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330,
349, 392, 440, 494,
@@ -287,43 +261,16 @@ uint16_t taurustones[28] = { 165, 175, 196, 220,
330, 284, 262, 247,
220, 196, 175, 165 };
#endif
#endif
void MotorDriver::setDCSignal(byte speedcode) {
if (brakePin == UNUSED_PIN)
return;
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO)
// 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.
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
#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;
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs
#endif
default:
break;
}
// spedcoode is a dcc speed & direction
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
byte tDir=speedcode & 0x80;
@@ -331,14 +278,12 @@ void MotorDriver::setDCSignal(byte speedcode) {
#if defined(ARDUINO_ARCH_ESP32)
{
int f = 131;
#ifdef VARIABLE_TONES
if (tSpeed > 2) {
if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ;
}
}
#endif
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
}
#endif
if (tSpeed <= 1) brake = 255;
@@ -347,7 +292,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
if (invertBrake)
brake=255-brake;
#if defined(ARDUINO_ARCH_ESP32)
DCCTimer::DCCEXanalogWrite(brakePin,brake);
DCCEXanalogWrite(brakePin,brake);
#else
analogWrite(brakePin,brake);
#endif
@@ -376,60 +321,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
interrupts();
}
}
void MotorDriver::throttleInrush(bool on) {
if (brakePin == UNUSED_PIN)
return;
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
return;
byte duty = on ? 208 : 0;
if (invertBrake)
duty = 255-duty;
#if defined(ARDUINO_ARCH_ESP32)
if(on) {
DCCTimer::DCCEXanalogWrite(brakePin,duty);
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
} else {
ledcDetachPin(brakePin);
}
#else
if(on){
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO)
// Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
case 9:
case 10:
// Timer2 (is different)
TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM
TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max)
DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
#endif
default:
break;
}
}
analogWrite(brakePin,duty);
#endif
}
unsigned int MotorDriver::raw2mA( int raw) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
return (int32_t)raw * senseFactorInternal / senseScale;
@@ -458,170 +350,64 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
}
///////////////////////////////////////////////////////////////////////////////////////////
// checkPowerOverload(useProgLimit, trackno)
// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
// byte trackno: trackmanager knows it's number (could be skipped?)
//
// Short ciruit handling strategy:
//
// There are the following power states: ON ALERT OVERLOAD OFF
// OFF state is only changed to/from manually. Power is on
// during ON and ALERT. Power is off during OVERLOAD and OFF.
// The overload mechanism changes between the other states like
//
// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON
// or
// ON -1-> ALERT -4-> ON
//
// Times are in class MotorDriver (MotorDriver.h).
//
// 1. ON to ALERT:
// Transition on fault pin condition or current overload
//
// 2. ALERT to OVERLOAD:
// Transition happens if different timeouts have elapsed.
// If only the fault pin is active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms)
// If only overcurrent is detected, timeout is
// POWER_SAMPLE_IGNORE_CURRENT (100ms)
// If fault pin and overcurrent are active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms)
// Transition to OVERLOAD turns off power to the affected
// output (unless fault pins are shared)
// If the transition conditions are not fullfilled,
// transition according to 4 is tested.
//
// 3. OVERLOAD to ALERT
// Transiton happens when timeout has elapsed, timeout
// is named power_sample_overload_wait. It is started
// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry
// to OVERLOAD and then increased by a factor of 2
// at further entries to the OVERLOAD condition. This
// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached.
// power_sample_overload_wait is reset by a poweroff or
// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON.
// After timeout power is turned on again and state
// goes back to ALERT.
//
// 4. ALERT to ON
// Transition happens by watching the current and fault pin
// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If
// values have been good during that time, transition is
// made back to ON. Note that even if state is back to ON,
// the power_sample_overload_wait time is first reset
// later (see above).
//
// The time keeping is handled by timestamps lastPowerChange[]
// which are set by each power change and by lastBadSample which
// keeps track if conditions during ALERT have been good enough
// to go back to ON. The time differences are calculated by
// microsSinceLastPowerChange().
//
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
if (millis() - lastSampleTaken < sampleDelay) return;
lastSampleTaken = millis();
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
// Trackname for diag messages later
switch (powerMode) {
case POWERMODE::OFF: {
lastPowerMode = POWERMODE::OFF;
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
break;
}
case POWERMODE::ON: {
lastPowerMode = POWERMODE::ON;
bool cF = checkFault();
bool cC = checkCurrent(useProgLimit);
if(cF || cC ) {
if (cC) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A',
cF ? "FAULT" : "",
mA);
case POWERMODE::OFF:
sampleDelay = POWER_SAMPLE_OFF_WAIT;
break;
case POWERMODE::ON:
// Check current
lastCurrent=getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
lastCurrent = -lastCurrent;
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
if (commonFaultPin) {
if (lastCurrent < tripValue) {
setPower(POWERMODE::ON); // maybe other track
}
// Write this after the fact as we want to turn on as fast as possible
// because we don't know which output actually triggered the fault pin
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A');
} else {
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
if (lastCurrent < tripValue) {
lastCurrent = tripValue; // exaggerate
}
}
}
if (lastCurrent < tripValue) {
sampleDelay = POWER_SAMPLE_ON_WAIT;
if(power_good_counter<100)
power_good_counter++;
else
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
} else {
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
setPower(POWERMODE::OVERLOAD);
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
power_good_counter=0;
sampleDelay = power_sample_overload_wait;
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay);
if (power_sample_overload_wait >= 10000)
power_sample_overload_wait = 10000;
else
power_sample_overload_wait *= 2;
}
setPower(POWERMODE::ALERT);
break;
}
// all well
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
break;
}
case POWERMODE::ALERT: {
// set local flags that handle how much is output to diag (do not output duplicates)
bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
bool powerModeChange = (powerMode != lastPowerMode);
unsigned long now = micros();
if (powerModeChange)
lastBadSample = now;
lastPowerMode = POWERMODE::ALERT;
// check how long we have been in this state
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
if(checkFault()) {
throttleInrush(true);
lastBadSample = now;
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
if ( mslpc < timeout) {
if (powerModeChange)
DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout);
break;
}
DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
}
if (checkCurrent(useProgLimit)) {
lastBadSample = now;
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
if (powerModeChange) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA);
}
break;
}
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
}
// all well
unsigned long goodtime = micros() - lastBadSample;
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
if (true || notFromOverload) { // we did a RESTORE message XXX
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
}
throttleInrush(false);
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
setPower(POWERMODE::ON);
}
break;
}
case POWERMODE::OVERLOAD: {
lastPowerMode = POWERMODE::OVERLOAD;
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
if (mslpc > power_sample_overload_wait) {
// adjust next wait time
power_sample_overload_wait *= 2;
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
// power on test
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
setPower(POWERMODE::ALERT);
}
break;
}
default:
break;
sampleDelay = POWER_SAMPLE_ON_WAIT;
// Debug code....
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
break;
default:
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
}
}

View File

@@ -27,10 +27,6 @@
#include "IODevice.h"
#include "DCCTimer.h"
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
@@ -78,9 +74,8 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
// Virtualised Motor shield 1-track hardware Interface
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
#define UNUSED_PIN 255 // inside uint8_t
#define UNUSED_PIN 127 // inside int8_t
#endif
#define MAX_PIN 254
class pinpair {
public:
@@ -111,13 +106,13 @@ extern volatile portreg_t shadowPORTA;
extern volatile portreg_t shadowPORTB;
extern volatile portreg_t shadowPORTC;
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
class MotorDriver {
public:
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, int16_t fault_pin);
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
void setPower( POWERMODE mode);
POWERMODE getPower() { return powerMode;}
// as the port registers can be shadowed to get syncronized DCC signals
@@ -149,7 +144,6 @@ class MotorDriver {
};
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte);
void throttleInrush(bool on);
inline void detachDCSignal() {
#if defined(__arm__)
pinMode(brakePin, OUTPUT);
@@ -180,10 +174,7 @@ class MotorDriver {
bool isPWMCapable();
bool canMeasureCurrent();
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs
inline byte setCommonFaultPin() {
return commonFaultPin = true;
}
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
inline byte getFaultPin() {
return faultPin;
}
@@ -194,53 +185,23 @@ class MotorDriver {
inline void setTrackLetter(char c) {
trackLetter = c;
};
// this returns how much time has passed since the last power change. If it
// was really long ago (approx > 52min) advance counter approx 35 min so that
// we are at 18 minutes again. Times for 32 bit unsigned long.
inline unsigned long microsSinceLastPowerChange(POWERMODE mode) {
unsigned long now = micros();
unsigned long diff = now - lastPowerChange[(int)mode];
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
return diff;
};
#ifdef ANALOG_READ_INTERRUPT
bool sampleCurrentFromHW();
void startCurrentFromHW();
#endif
inline void setMode(TRACK_MODE m) {
trackMode = m;
};
inline TRACK_MODE getMode() {
return trackMode;
};
private:
char trackLetter = '?';
bool isProgTrack = false; // tells us if this is a prog track
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
inline void getFastPin(const FSH* type,int pin, FASTPIN & result) {
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
getFastPin(type, pin, 0, result);
};
// side effect sets lastCurrent and tripValue
inline bool checkCurrent(bool useProgLimit) {
tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
lastCurrent = getCurrentRaw();
if (lastCurrent < 0)
lastCurrent = -lastCurrent;
return lastCurrent >= tripValue;
};
// side effect sets lastCurrent
inline bool checkFault() {
lastCurrent = getCurrentRaw();
return lastCurrent < 0;
};
}
VPIN powerPin;
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
bool dualSignal; // true to use signalPin2
bool invertBrake; // brake 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
// Raw to milliamp conversion factors avoiding float data types.
// Milliamps=rawADCreading * sensefactorInternal / senseScale
@@ -254,14 +215,10 @@ class MotorDriver {
int rawCurrentTripValue;
// current sampling
POWERMODE powerMode;
POWERMODE lastPowerMode;
unsigned long lastPowerChange[4]; // timestamp in microseconds
unsigned long lastBadSample; // timestamp in microseconds
// used to sync restore time when common Fault pin detected
static unsigned long globalOverloadStart; // timestamp in microseconds
unsigned long lastSampleTaken;
unsigned int sampleDelay;
int progTripValue;
int lastCurrent; //temp value
int tripValue; //temp value
int lastCurrent;
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
@@ -269,28 +226,16 @@ class MotorDriver {
int maxmA;
int tripmA;
// Times for overload management. Unit: microseconds.
// Base for wait time until power is turned on again
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL;
// Time after we consider all faults old and forgotten
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
// Time after which we consider a ALERT over
static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL;
// How long to ignore fault pin if current is under limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 100000UL;
// How long to ignore fault pin if current is higher than limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL;
// How long to wait between overcurrent and turning off
static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL;
// Upper limit for retry period
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
// Wait times for power management. Unit: milliseconds
static const int POWER_SAMPLE_ON_WAIT = 100;
static const int POWER_SAMPLE_OFF_WAIT = 1000;
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
// Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either.
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
TRACK_MODE trackMode = TRACK_MODE_NONE; // we assume track not assigned at startup
};
#endif

View File

@@ -1,7 +1,7 @@
/*
* © 2022-2023 Paul M. Antoine
* © 2022 Paul M. Antoine
* © 2021 Fred Decker
* © 2020-2023 Harald Barth
* © 2020-2022 Harald Barth
* (c) 2020 Chris Harlow. All rights reserved.
* (c) 2021 Fred Decker. All rights reserved.
* (c) 2020 Harald Barth. All rights reserved.
@@ -36,7 +36,7 @@
// custom defines in config.h.
#ifndef UNUSED_PIN // sync define with the one in MotorDriver.h
#define UNUSED_PIN 255 // inside uint8_t
#define UNUSED_PIN 127 // inside int8_t
#endif
// The MotorDriver definition is:
@@ -60,8 +60,7 @@
// Arduino STANDARD Motor Shield, used on different architectures:
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
// Standard Motor Shield definition for 3v3 processors (other than the ESP32)
// Setup for SAMD21 Sparkfun DEV board MUST use Arduino Motor Shield R3 (MUST be R3
// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3
// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
// to tweak both these
@@ -71,27 +70,15 @@
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC
#define EX8874_SHIELD F("EX8874"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, A5)
#elif defined(ARDUINO_ARCH_ESP32)
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
// analog inputs. Here we use analog inputs A2 and A3 as A0 and A1 are wired in a way so that
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN)
// EX 8874 based shield connected to a 3.3V system (like ESP32) and 12bit (4096) ADC
// numbers are GPIO numbers. comments are UNO form factor shield pin numbers
#define EX8874_SHIELD F("EX8874"),\
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, 36 /*A4*/), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, 39 /*A5*/)
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN)
#else
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
@@ -101,12 +88,6 @@
#define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \
new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \
new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN)
// EX 8874 based shield connected to a 5V system (like Arduino) and 10bit (1024) ADC
#define EX8874_SHIELD F("EX8874"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, A5)
#endif
// Pololu Motor Shield

View File

@@ -87,9 +87,6 @@ void SerialManager::init() {
delay(1000);
}
#endif
#ifdef SABERTOOTH
Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
#endif
}
void SerialManager::broadcast(char * stringBuffer) {

View File

@@ -117,24 +117,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
case 'o': stream->print(va_arg(args, int), OCT); break;
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
case 'M':
{ // this prints a unsigned long microseconds time in readable format
unsigned long time = va_arg(args, long);
if (time >= 2000) {
time = time / 1000;
if (time >= 2000) {
printPadded(stream, time/1000, formatWidth, formatLeft);
stream->print(F("sec"));
} else {
printPadded(stream,time, formatWidth, formatLeft);
stream->print(F("msec"));
}
} else {
printPadded(stream,time, formatWidth, formatLeft);
stream->print(F("usec"));
}
}
break;
//case 'f': stream->print(va_arg(args, double), 2); break;
//format width prefix
case '-':

View File

@@ -31,20 +31,19 @@
#define APPLY_BY_MODE(findmode,function) \
FOR_EACH_TRACK(t) \
if (track[t]->getMode()==findmode) \
if (trackMode[t]==findmode) \
track[t]->function;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_PROG = -29718;
#endif
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_OFF = 22479;
const int16_t HASH_KEYWORD_NONE = -26550;
const int16_t HASH_KEYWORD_DC = 2183;
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
MotorDriver * TrackManager::track[MAX_TRACKS];
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
@@ -53,7 +52,7 @@ bool TrackManager::progTrackSyncMain=false;
bool TrackManager::progTrackBoosted=false;
int16_t TrackManager::joinRelay=UNUSED_PIN;
#ifdef ARDUINO_ARCH_ESP32
byte TrackManager::tempProgTrack=MAX_TRACKS+1; // MAX_TRACKS+1 is the unused flag
byte TrackManager::tempProgTrack=MAX_TRACKS+1;
#endif
#ifdef ANALOG_READ_INTERRUPT
@@ -74,7 +73,7 @@ void TrackManager::sampleCurrent() {
waiting = false;
tr++;
if (tr > lastTrack) tr = 0;
if (lastTrack < 2 || track[tr]->getMode() & TRACK_MODE_PROG) {
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
return; // We could continue but for prog track we
// rather do it in next interrupt beacuse
// that gives us well defined sampling point.
@@ -85,7 +84,7 @@ void TrackManager::sampleCurrent() {
if (!waiting) {
// look for a valid track to sample or until we are around
while (true) {
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
track[tr]->startCurrentFromHW();
// for scope debug track[1]->setBrake(1);
waiting = true;
@@ -117,31 +116,19 @@ void TrackManager::Setup(const FSH * shieldname,
// Default the first 2 tracks (which may be null) and perform HA waveform check.
setTrackMode(0,TRACK_MODE_MAIN);
#ifndef DISABLE_PROG
setTrackMode(1,TRACK_MODE_PROG);
#else
setTrackMode(1,TRACK_MODE_MAIN);
#endif
// Fault pin config for odd motor boards (example pololu)
FOR_EACH_TRACK(t) {
for (byte s=t+1;s<=lastTrack;s++) {
if (track[t]->getFaultPin() != UNUSED_PIN &&
track[t]->getFaultPin() == track[s]->getFaultPin()) {
track[t]->setCommonFaultPin();
track[s]->setCommonFaultPin();
DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A');
}
}
}
DCC::setShieldName(shieldname);
// TODO Fault pin config for odd motor boards (example pololu)
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
// && (mainDriver->getFaultPin() != UNUSED_PIN));
DCC::begin(shieldname);
}
void TrackManager::addTrack(byte t, MotorDriver* driver) {
trackMode[t]=TRACK_MODE_OFF;
track[t]=driver;
if (driver) {
track[t]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE);
track[t]->setTrackLetter('A'+t);
lastTrack=t;
}
@@ -182,27 +169,22 @@ void TrackManager::setPROGSignal( bool on) {
// with interrupts turned off around the critical section
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
FOR_EACH_TRACK(t) {
if (trackDCAddr[t]!=cab && cab != 0) continue;
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
if (trackDCAddr[t]!=cab) continue;
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
}
}
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
//DIAG(F("Track=%c"),trackToSet+'A');
// DC tracks require a motorDriver that can set brake!
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
#if defined(ARDUINO_AVR_UNO)
DIAG(F("Uno has no PWM timers available for DC"));
return false;
#endif
if (!track[trackToSet]->brakeCanPWM()) {
DIAG(F("Brake pin can't PWM: No DC"));
return false;
}
}
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
&& !track[trackToSet]->brakeCanPWM()) {
DIAG(F("Brake pin can't PWM: No DC"));
return false;
}
#ifdef ARDUINO_ARCH_ESP32
// remove pin from MUX matrix and turn it off
@@ -216,16 +198,12 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
}
#endif
#ifndef DISABLE_PROG
if (mode==TRACK_MODE_PROG) {
#else
if (false) {
#endif
// only allow 1 track to be prog
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
track[t]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE);
trackMode[t]=TRACK_MODE_OFF;
track[t]->makeProgTrack(false); // revoke prog track special handling
streamTrackState(NULL,t);
}
@@ -233,7 +211,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
} else {
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
}
track[trackToSet]->setMode(mode);
trackMode[trackToSet]=mode;
trackDCAddr[trackToSet]=dcAddr;
streamTrackState(NULL,trackToSet);
@@ -260,7 +238,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// DC tracks must not have the DCC PWM switched on
// so we globally turn it off if one of the PWM
// capable tracks is now DC or DCX.
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
if (track[t]->isPWMCapable()) {
canDo=false; // this track is capable but can not run PWM
break; // in this mode, so abort and prevent globally below
@@ -268,7 +246,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
track[t]->trackPWM=false; // this track sure can not run with PWM
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
}
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
canDo &= track[t]->trackPWM;
@@ -306,7 +284,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
void TrackManager::applyDCSpeed(byte t) {
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
if (track[t]->getMode()==TRACK_MODE_DCX)
if (trackMode[t]==TRACK_MODE_DCX)
speedByte = speedByte ^ 128; // reverse direction bit
track[t]->setDCSignal(speedByte);
}
@@ -328,13 +306,11 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
return setTrackMode(p[0],TRACK_MODE_MAIN);
#ifndef DISABLE_PROG
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
return setTrackMode(p[0],TRACK_MODE_PROG);
#endif
if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE>
return setTrackMode(p[0],TRACK_MODE_NONE);
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
return setTrackMode(p[0],TRACK_MODE_OFF);
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
return setTrackMode(p[0],TRACK_MODE_EXT);
@@ -352,17 +328,15 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
// null stream means send to commandDistributor for broadcast
if (track[t]==NULL) return;
auto format=F("");
switch(track[t]->getMode()) {
switch(trackMode[t]) {
case TRACK_MODE_MAIN:
format=F("<= %c MAIN>\n");
break;
#ifndef DISABLE_PROG
case TRACK_MODE_PROG:
format=F("<= %c PROG>\n");
break;
#endif
case TRACK_MODE_NONE:
format=F("<= %c NONE>\n");
case TRACK_MODE_OFF:
format=F("<= %c OFF>\n");
break;
case TRACK_MODE_EXT:
format=F("<= %c EXT>\n");
@@ -383,22 +357,20 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
byte TrackManager::nextCycleTrack=MAX_TRACKS;
void TrackManager::loop() {
DCCWaveform::loop();
#ifndef DISABLE_PROG
DCCACK::loop();
#endif
DCCWaveform::loop();
DCCACK::loop();
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
nextCycleTrack++;
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
if (track[nextCycleTrack]==NULL) return;
MotorDriver * motorDriver=track[nextCycleTrack];
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
}
MotorDriver * TrackManager::getProgDriver() {
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
return NULL;
}
@@ -406,7 +378,7 @@ MotorDriver * TrackManager::getProgDriver() {
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
std::vector<MotorDriver *> v;
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]);
return v;
}
#endif
@@ -416,7 +388,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
FOR_EACH_TRACK(t) {
MotorDriver * driver=track[t];
if (!driver) continue;
switch (track[t]->getMode()) {
switch (trackMode[t]) {
case TRACK_MODE_MAIN:
if (setProg) break;
// toggle brake before turning power on - resets overcurrent error
@@ -444,7 +416,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_NONE:
case TRACK_MODE_OFF:
break;
}
}
@@ -452,8 +424,8 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
POWERMODE TrackManager::getProgPower() {
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG)
return track[t]->getPower();
if (trackMode[t]==TRACK_MODE_PROG)
return track[t]->getPower();
return POWERMODE::OFF;
}
@@ -497,7 +469,7 @@ void TrackManager::setJoin(bool joined) {
#ifdef ARDUINO_ARCH_ESP32
if (joined) {
FOR_EACH_TRACK(t) {
if (track[t]->getMode()==TRACK_MODE_PROG) {
if (trackMode[t]==TRACK_MODE_PROG) {
tempProgTrack = t;
setTrackMode(t, TRACK_MODE_MAIN);
break;
@@ -505,12 +477,7 @@ void TrackManager::setJoin(bool joined) {
}
} else {
if (tempProgTrack != MAX_TRACKS+1) {
// as setTrackMode with TRACK_MODE_PROG defaults to
// power off, we will take the current power state
// of our track and then preserve that state.
POWERMODE tPTmode = track[tempProgTrack]->getPower(); //get current power status of this track
setTrackMode(tempProgTrack, TRACK_MODE_PROG);
track[tempProgTrack]->setPower(tPTmode); //set track status as it was before
tempProgTrack = MAX_TRACKS+1;
}
}

View File

@@ -27,6 +27,10 @@
#include "MotorDriver.h"
// Virtualised Motor shield multi-track hardware Interface
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
@@ -80,15 +84,8 @@ class TrackManager {
static int16_t joinRelay;
static bool progTrackSyncMain; // true when prog track is a siding switched to main
static bool progTrackBoosted; // true when prog track is not current limited
#ifdef DEBUG_ADC
public:
#else
private:
#endif
static MotorDriver* track[MAX_TRACKS];
static bool progTrackBoosted; // true when prog track is not current limited
private:
static void addTrack(byte t, MotorDriver* driver);
static byte lastTrack;
@@ -96,6 +93,8 @@ class TrackManager {
static POWERMODE mainPowerGuess;
static void applyDCSpeed(byte t);
static MotorDriver* track[MAX_TRACKS];
static TRACK_MODE trackMode[MAX_TRACKS];
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
#ifdef ARDUINO_ARCH_ESP32
static byte tempProgTrack; // holds the prog track number during join

View File

@@ -36,10 +36,6 @@
#include "LCN.h"
#ifdef EESTOREDEBUG
#include "DIAG.h"
#endif
#ifndef IO_NO_HAL
#include "IO_ScheduledPin.h"
#endif
/*
@@ -191,10 +187,6 @@
// VPIN turnout
tt = VpinTurnout::load(&turnoutData);
break;
case TURNOUT_HBRIDGE:
// HBRIDGE turnout
tt = HBridgeTurnout::load(&turnoutData);
break;
default:
// If we find anything else, then we don't know what it is or how long it is,
// so we can't go any further through the EEPROM!
@@ -258,7 +250,6 @@
}
}
tt = (Turnout *)new ServoTurnout(id, vpin, thrownPosition, closedPosition, profile, closed);
DIAG(F("Turnout 0x%x size %d size %d"), tt, sizeof(Turnout),sizeof(struct TurnoutData));
IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant);
return tt;
#else
@@ -485,102 +476,6 @@
#endif
}
/*************************************************************************************
* HBridgeTurnout - Turnout controlled through a pair of HAL pins.
* Typically connected to Motor H-Bridge. Delay is used to quickly turn on/off power.
*************************************************************************************/
// Constructor
HBridgeTurnout::HBridgeTurnout(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed) :
Turnout(id, TURNOUT_HBRIDGE, closed)
{
_hbridgeTurnoutData.pin1 = pin1;
_hbridgeTurnoutData.pin2 = pin2;
_hbridgeTurnoutData.millisDelay = millisDelay;
#ifndef IO_NO_HAL
// HARD LIMIT to maximum 0.5 second to avoid burning the coil
// Also note 1000x multiplier because ScheduledPin works with microSeconds.
ScheduledPin::create(pin1, LOW, 1000*min(millisDelay, 500));
ScheduledPin::create(pin2, LOW, 1000*min(millisDelay, 500));
#else
DIAG(F("H-Brdige Turnout %d will be disabled because HAL is off"), id);
#endif
}
// Create function
/* static */ Turnout *HBridgeTurnout::create(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed) {
Turnout *tt = get(id);
if (tt) {
// Object already exists, check if it is usable
if (tt->isType(TURNOUT_HBRIDGE)) {
// Yes, so set parameters
HBridgeTurnout *hbt = (HBridgeTurnout *)tt;
hbt->_hbridgeTurnoutData.pin1 = pin1;
hbt->_hbridgeTurnoutData.pin2 = pin2;
hbt->_hbridgeTurnoutData.millisDelay = millisDelay;
// Don't touch the _closed parameter, retain the original value.
return tt;
} else {
// Incompatible object, delete and recreate
remove(id);
}
}
tt = (Turnout *)new HBridgeTurnout(id, pin1, pin2, millisDelay, closed);
return tt;
}
// Load a VPIN turnout definition from EEPROM. The common Turnout data has already been read at this point.
/* static */ Turnout *HBridgeTurnout::load(struct TurnoutData *turnoutData) {
#ifndef DISABLE_EEPROM
HBridgeTurnoutData hbridgeTurnoutData;
// Read class-specific data from EEPROM
EEPROM.get(EEStore::pointer(), hbridgeTurnoutData);
EEStore::advance(sizeof(hbridgeTurnoutData));
// Create new object
HBridgeTurnout *tt = new HBridgeTurnout(turnoutData->id, hbridgeTurnoutData.pin1,
hbridgeTurnoutData.pin2, hbridgeTurnoutData.millisDelay, turnoutData->closed);
return tt;
#else
(void)turnoutData;
return NULL;
#endif
}
// Report 1 for thrown, 0 for closed.
void HBridgeTurnout::print(Print *stream) {
StringFormatter::send(stream, F("<H %d HBRIDGE %d %d %d>\n"), _turnoutData.id, _hbridgeTurnoutData.pin1, _hbridgeTurnoutData.pin2,
!_turnoutData.closed);
}
void HBridgeTurnout::turnUpDown(VPIN pin) {
// HBridge turnouts require very small, prescribed time to keep pin1 or pin2 in HIGH state.
// Otherwise internal coil of the turnout will burn.
// If HAL is disabled (and therefore SchedulePin class), we can not turn this on,
// otherwise coil will burn and device will be lost.
#ifndef IO_NO_HAL
IODevice::write(pin, HIGH);
#endif
}
bool HBridgeTurnout::setClosedInternal(bool close) {
turnUpDown(close ? _hbridgeTurnoutData.pin2 : _hbridgeTurnoutData.pin1);
_turnoutData.closed = close;
return true;
}
void HBridgeTurnout::save() {
#ifndef DISABLE_EEPROM
// Write turnout definition and current position to EEPROM
// First write common servo data, then
// write the servo-specific data
EEPROM.put(EEStore::pointer(), _turnoutData);
EEStore::advance(sizeof(_turnoutData));
EEPROM.put(EEStore::pointer(), _hbridgeTurnoutData);
EEStore::advance(sizeof(_hbridgeTurnoutData));
#endif
}
/*************************************************************************************
* LCNTurnout - Turnout controlled by Loconet

View File

@@ -37,7 +37,6 @@ enum {
TURNOUT_SERVO = 2,
TURNOUT_VPIN = 3,
TURNOUT_LCN = 4,
TURNOUT_HBRIDGE = 5,
};
/*************************************************************************************
@@ -70,12 +69,10 @@ protected:
uint16_t id;
} _turnoutData; // 3 bytes
#ifndef DISABLE_EEPROM
// Address in eeprom of first byte of the _turnoutData struct (containing the closed flag).
// Set to zero if the object has not been saved in EEPROM, e.g. for newly created Turnouts, and
// for all LCN turnouts.
uint16_t _eepromAddress = 0;
#endif
// Pointer to next turnout on linked list.
Turnout *_nextTurnout = 0;
@@ -285,41 +282,6 @@ protected:
};
/*************************************************************************************
* HBridgeTurnout - Turnout controlled through a pair of HAL pins.
*
* Hard limited to maximum 0.5 second to avoid burning the coil
* Typical millisDelay should be within between 50 and 100
*************************************************************************************/
class HBridgeTurnout : public Turnout {
private:
// HBridgeTurnoutData contains data specific to this subclass that is
// written to EEPROM when the turnout is saved.
struct HBridgeTurnoutData {
VPIN pin1;
VPIN pin2;
uint16_t millisDelay;
} _hbridgeTurnoutData; // 6 bytes
// Constructor
HBridgeTurnout(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed);
public:
// Create function
static Turnout *create(uint16_t id, VPIN pin1, VPIN pin2, uint16_t millisDelay, bool closed=true);
// Load a HBRIDGE turnout definition from EEPROM. The common Turnout data has already been read at this point.
static Turnout *load(struct TurnoutData *turnoutData);
void print(Print *stream) override;
protected:
bool setClosedInternal(bool close) override;
void save() override;
private:
void turnUpDown(VPIN pin);
};
/*************************************************************************************
* LCNTurnout - Turnout controlled by Loconet

View File

@@ -235,10 +235,6 @@ int WiThrottle::getLocoId(byte * cmd) {
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
char throttleChar=cmd[1];
int locoid=getLocoId(cmd+3); // -1 for *
if (locoid > 10239 || locoid < -1) {
StringFormatter::send(stream, F("No valid DCC loco %d\n"), locoid);
return;
}
byte * aval=cmd;
while(*aval !=';' && *aval !='\0') aval++;
if (*aval) aval+=2; // skip ;>
@@ -531,13 +527,10 @@ void WiThrottle::sendRoster(Print* stream) {
rosterSent=true;
#ifdef EXRAIL_ACTIVE
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
for (int16_t r=0;;r++) {
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
if (cabid == INT16_MAX)
break;
if (cabid > 0)
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
}
StringFormatter::send(stream,F("\n"));
#else
@@ -551,14 +544,14 @@ void WiThrottle::sendRoutes(Print* stream) {
// first pass automations
for (int ix=0;;ix+=2) {
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
if (id==INT16_MAX) break;
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
}
// second pass routes.
for (int ix=0;;ix+=2) {
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
if (id==INT16_MAX) break;
if (id==0) break;
const FSH * desc=RMFT2::getRouteDescription(id);
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
}
@@ -574,13 +567,9 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
#ifdef EXRAIL_ACTIVE
const FSH * functionNames= RMFT2::getRosterFunctions(locoid);
if (functionNames == NULL) {
// no roster entry for locoid, try to find default entry
functionNames= RMFT2::getRosterFunctions(0);
}
if (functionNames == NULL) {
// no default roster entry either, use non-exrail presets as above
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
if (!functionNames) {
// no roster, use non-exrail presets as above
}
else if (GETFLASH(functionNames)=='\0') {
// "" = Roster but no functions given
@@ -595,7 +584,7 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
fkeys=0;
bool firstchar=true;
for (int fx=0;;fx++) {
char c=GETFLASH((char *)functionNames+fx);
char c=GETFLASH(functionNames+fx);
if (c=='\0') {
fkeys++;
break;

View File

@@ -1,7 +1,5 @@
/*
© 2023 Paul M. Antoine
© 2021 Harald Barth
© 2023 Nathan Kellenicki
© 2021, Harald Barth.
This file is part of CommandStation-EX
@@ -22,7 +20,6 @@
#if defined(ARDUINO_ARCH_ESP32)
#include <vector>
#include "defines.h"
#include "ESPmDNS.h"
#include <WiFi.h>
#include "esp_wifi.h"
#include "WifiESP32.h"
@@ -108,18 +105,11 @@ void wifiLoop(void *){
}
#endif
char asciitolower(char in) {
if (in <= 'Z' && in >= 'A')
return in - ('Z' - 'z');
return in;
}
bool WifiESP::setup(const char *SSid,
const char *password,
const char *hostname,
int port,
const byte channel,
const bool forceAP) {
const byte channel) {
bool havePassword = true;
bool haveSSID = true;
bool wifiUp = false;
@@ -147,8 +137,7 @@ bool WifiESP::setup(const char *SSid,
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
havePassword = false;
if (haveSSID && havePassword && !forceAP) {
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
if (haveSSID && havePassword) {
WiFi.mode(WIFI_STA);
#ifdef SERIAL_BT_COMMANDS
WiFi.setSleep(true);
@@ -185,20 +174,16 @@ bool WifiESP::setup(const char *SSid,
}
}
}
if (!haveSSID || forceAP) {
if (!haveSSID) {
// prepare all strings
String strSSID(forceAP ? SSid : "DCCEX_");
String strPass(forceAP ? password : "PASS_");
if (!forceAP) {
String strMac = WiFi.macAddress();
strMac.remove(0,9);
strMac.replace(":","");
strMac.replace(":","");
// convert mac addr hex chars to lower case to be compatible with AT software
std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower);
strSSID.concat(strMac);
strPass.concat(strMac);
}
String strSSID("DCC_");
String strPass("PASS_");
String strMac = WiFi.macAddress();
strMac.remove(0,9);
strMac.replace(":","");
strMac.replace(":","");
strSSID.concat(strMac);
strPass.concat(strMac);
WiFi.mode(WIFI_AP);
#ifdef SERIAL_BT_COMMANDS
@@ -224,15 +209,6 @@ bool WifiESP::setup(const char *SSid,
// no idea to go on
return false;
}
// Now Wifi is up, register the mDNS service
if(!MDNS.begin(hostname)) {
DIAG(F("Wifi setup failed to start mDNS"));
}
if(!MDNS.addService("withrottle", "tcp", 2560)) {
DIAG(F("Wifi setup failed to add withrottle service to mDNS"));
}
server = new WiFiServer(port); // start listening on tcp port
server->begin();
// server started here

View File

@@ -1,6 +1,5 @@
/*
* © 2021 Harald Barth
* © 2023 Nathan Kellenicki
* © 2021, Harald Barth.
*
* This file is part of CommandStation-EX
*
@@ -32,8 +31,7 @@ public:
const char *wifiPassword,
const char *hostname,
const int port,
const byte channel,
const bool forceAP);
const byte channel);
static void loop();
private:
};

View File

@@ -2,7 +2,6 @@
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2022 Chris Harlow
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -36,11 +35,6 @@ const unsigned long LOOP_TIMEOUT = 2000;
bool WifiInterface::connected = false;
Stream * WifiInterface::wifiStream;
#ifndef WIFI_AT_CHECK_TIMEOUT
// Some ESP32 AT firmware versions take time to initialize and do not respond to AT commands right away.
#define WIFI_AT_CHECK_TIMEOUT 2000
#endif
#ifndef WIFI_CONNECT_TIMEOUT
// Tested how long it takes to FAIL an unknown SSID on firmware 1.7.4.
// The ES should fail a connect in 15 seconds, we don't want to fail BEFORE that
@@ -58,32 +52,10 @@ Stream * WifiInterface::wifiStream;
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
#define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial3
#endif
#if defined(ARDUINO_ARCH_STM32)
// Handle serial ports availability on STM32 for variants!
// #undef NUM_SERIAL
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial6
#elif defined(ARDUINO_NUCLEO_F446RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial3
#define SERIAL3 Serial5
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG)
#define NUM_SERIAL 2
#define SERIAL1 Serial6
#else
#warning This variant of Nucleo not yet explicitly supported
#endif
#endif
#ifndef NUM_SERIAL
#define NUM_SERIAL 1
#define SERIAL1 Serial1
#endif
bool WifiInterface::setup(long serial_link_speed,
@@ -91,8 +63,7 @@ bool WifiInterface::setup(long serial_link_speed,
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel,
const bool forceAP) {
const byte channel) {
wifiSerialState wifiUp = WIFI_NOAT;
@@ -104,34 +75,27 @@ bool WifiInterface::setup(long serial_link_speed,
(void) hostname;
(void) port;
(void) channel;
(void) forceAP;
#endif
// See if the WiFi is attached to the first serial port
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
SERIAL1.begin(serial_link_speed);
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
Serial1.begin(serial_link_speed);
wifiUp = setup(Serial1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
// Currently only the Arduino Mega 2560 has usable Serial2 (Nucleo-64 boards use Serial 2 for console!)
#if defined(ARDUINO_AVR_MEGA2560)
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
#endif
// We guess here that in all architctures that have a Serial3
// we can use it for our purpose.
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
if (wifiUp == WIFI_NOAT)
{
SERIAL3.begin(serial_link_speed);
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
Serial3.begin(serial_link_speed);
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
@@ -149,7 +113,7 @@ bool WifiInterface::setup(long serial_link_speed,
}
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel, bool forceAP) {
const FSH* hostname, int port, byte channel) {
wifiSerialState wifiState;
static uint8_t ntry = 0;
ntry++;
@@ -158,7 +122,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
wifiState = setup2( SSid, password, hostname, port, channel, forceAP);
wifiState = setup2( SSid, password, hostname, port, channel);
if (wifiState == WIFI_NOAT) {
LCD(4, F("WiFi no AT chip"));
@@ -182,7 +146,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel, bool forceAP) {
const FSH* hostname, int port, byte channel) {
bool ipOK = false;
bool oldCmd = false;
@@ -197,7 +161,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
}
StringFormatter::send(wifiStream, F("AT\r\n")); // Is something here that understands AT?
if(!checkForOK(WIFI_AT_CHECK_TIMEOUT, true))
if(!checkForOK(200, true))
return WIFI_NOAT; // No AT compatible WiFi module here
StringFormatter::send(wifiStream, F("ATE1\r\n")); // Turn on the echo, se we can see what's happening
@@ -205,23 +169,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// Display the AT version information
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
if (checkForOK(2000, F("AT version:"), true, false)) {
char version[] = "0.0.0.0-xxx";
for (int i=0; i<11;i++) {
while(!wifiStream->available());
version[i]=wifiStream->read();
StringFormatter::printEscape(version[i]);
}
if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0'
&& version[7] == '-' && version[8] == 'd' && version[9] == 'e' && version[10] == 'v')) {
DIAG(F("You need to up/downgrade the ESP firmware"));
SSid = F("UPDATE_ESP_FIRMWARE");
forceAP = true;
}
}
checkForOK(2000, true, false);
checkForOK(2000, true, false); // Makes this visible on the console
#ifdef DONT_TOUCH_WIFI_CONF
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
@@ -251,7 +199,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
} else if (!forceAP) {
} else {
// SSID was configured, so we assume station (client) mode.
if (oldCmd) {
// AT command early version supports CWJAP/CWSAP
@@ -311,19 +259,14 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
i=0;
do {
if (!forceAP) {
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
oldCmd ? "" : "_CUR", macTail, macTail, channel);
} else {
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel);
}
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
oldCmd ? "" : "_CUR", macTail, macTail, channel);
} else {
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"),
oldCmd ? "" : "_CUR", SSid, password, channel);
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel);
}
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
if (i >= 2)

View File

@@ -1,7 +1,6 @@
/*
* © 2020-2021 Chris Harlow
* © 2020, Harald Barth.
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -37,18 +36,17 @@ public:
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel,
const bool forceAP);
const byte channel);
static void loop();
static void ATCommand(HardwareSerial * stream,const byte *command);
private:
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel, bool forceAP);
const FSH *hostname, int port, byte channel);
static Stream *wifiStream;
static DCCEXParser parser;
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel, bool forceAP);
const FSH *hostname, int port, byte channel);
static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true);
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
static bool connected;

View File

@@ -1,10 +1,9 @@
/*
* © 2022 Paul M. Antoine
* © 2021 Neil McKechnie
* © 2020-2023 Harald Barth
* © 2020-2021 Harald Barth
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2023 Nathan Kellenicki
*
* This file is part of CommandStation-EX
*
@@ -28,16 +27,6 @@ The configuration file for DCC-EX Command Station
**********************************************************************/
/////////////////////////////////////////////////////////////////////////////////////
// If you want to add your own motor driver definition(s), add them here
// For example MY_SHIELD with display name "MINE":
// (remove comment start and end marker if you want to edit and use that)
/*
#define MY_SHIELD F("MINE"), \
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5)
*/
/////////////////////////////////////////////////////////////////////////////////////
// NOTE: Before connecting these boards and selecting one in this software
// check the quick install guides!!! Some of these boards require a voltage
@@ -45,34 +34,19 @@ The configuration file for DCC-EX Command Station
// the correct resistor could damage the sense pin on your Arduino or destroy
// the device.
//
// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
//
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
// FIREBOX_MK1 : The Firebox MK1
// FIREBOX_MK1S : The Firebox MK1S
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield
// |
// +-----------------------v
//
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
//
/////////////////////////////////////////////////////////////////////////////////////
//
// If you want to restrict the maximum current LOWER than what your
// motor shield can provide, you can do that here. For example if you
// have a motor shield that can provide 5A and your power supply can
// only provide 2.5A then you should restict the maximum current to
// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before
// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example
// it would be 2250, adjust the number according to your PS. If your
// PS has a higher rating than your motor shield you do not need this.
// You can use this as well if you are cautious and your trains do not
// need full current.
// #define MAX_CURRENT 2250
//
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
@@ -124,11 +98,6 @@ The configuration file for DCC-EX Command Station
// this line exists or not. If you need to use an alternate channel (we recommend
// using only 1,6, or 11) you may change it here.
#define WIFI_CHANNEL 1
//
// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this
// true. Otherwise it is assumed that you'd like to connect to an existing network
// with that SSID.
#define WIFI_FORCE_AP false
/////////////////////////////////////////////////////////////////////////////////////
//
@@ -159,7 +128,7 @@ The configuration file for DCC-EX Command Station
//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied)
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
// Use 132,64 for a SH1106-based I2C device with a 128x64 display.
// #define OLED_DRIVER 0x3c,128,32
// #define OLED_DRIVER 128,32,0x3c
// Define scroll mode as 0, 1 or 2
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
@@ -172,7 +141,7 @@ The configuration file for DCC-EX Command Station
//
// If you do not need the EEPROM at all, you can disable all the code that saves
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
// and want to use the EXRAIL automation. Otherwise you do not have enough RAM
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands work.
//
// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled,
@@ -180,17 +149,6 @@ The configuration file for DCC-EX Command Station
//
// #define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE PROG
//
// If you do not need programming capability, you can disable all programming related
// commands. You might want to do that if you are using an Arduino UNO and still want
// to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both.
//
// Note this disables all programming functionality, including EXRAIL.
//
// #define DISABLE_PROG
/////////////////////////////////////////////////////////////////////////////////////
// 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
@@ -266,15 +224,5 @@ The configuration file for DCC-EX Command Station
//
//#define SERIAL_BT_COMMANDS
// SABERTOOTH
//
// This is a very special option and only useful if you happen to have a
// sabertooth motor controller from dimension engineering configured to
// take commands from and ESP32 via serial at 9600 baud from GPIO17 (TX)
// and GPIO16 (RX, currently unused).
// The number defined is the DCC address for which speed controls are sent
// to the sabertooth controller _as_well_. Default: Undefined.
//
//#define SABERTOOTH 1
/////////////////////////////////////////////////////////////////////////////////////

169
config.h.txt Normal file
View File

@@ -0,0 +1,169 @@
/**********************************************************************
Config.h
COPYRIGHT (c) 2013-2016 Gregg E. Berman
COPYRIGHT (c) 2020 Fred Decker
The configuration file for DCC++ EX Command Station
**********************************************************************/
/////////////////////////////////////////////////////////////////////////////////////
// NOTE: Before connecting these boards and selecting one in this software
// check the quick install guides!!! Some of these boards require a voltage
// generating resitor on the current sense pin of the device. Failure to select
// the correct resistor could damage the sense pin on your Arduino or destroy
// the device.
//
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
//
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
// FIREBOX_MK1 : The Firebox MK1
// FIREBOX_MK1S : The Firebox MK1S
// |
// +-----------------------v
//
// #define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"),
// new MotorDriver(3, 12, UNUSED_PIN, 9, A0, 0.488, 1500, UNUSED_PIN),
// new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 0.488, 1500, UNUSED_PIN)
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
//
#define IP_PORT 2560
/////////////////////////////////////////////////////////////////////////////////////
//
// NOTE: Only supported on Arduino Mega
// Set to false if you not even want it on the Arduino Mega
//
//#define ENABLE_WIFI true
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE WiFi Parameters (only in effect if WIFI is on)
//
// If DONT_TOUCH_WIFI_CONF is set, all WIFI config will be done with
// the <+> commands and this sketch will not change anything over
// AT commands and the other WIFI_* defines below do not have any effect.
//#define DONT_TOUCH_WIFI_CONF
//
// WIFI_SSID is the network name IF you want to use your existing home network.
// Do NOT change this if you want to use the WiFi in Access Point (AP) mode.
//
// If you do NOT set the WIFI_SSID, the WiFi chip will first try
// to connect to the previously configured network and if that fails
// fall back to Access Point mode. The SSID of the AP will be
// automatically set to DCCEX_*.
//
// Your SSID may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_SSID "Your network name"
//
// WIFI_PASSWORD is the network password for your home network or if
// you want to change the password from default AP mode password
// to the AP password you want.
// Your password may not conain ``"'' (double quote, ASCII 0x22).
#define WIFI_PASSWORD "deadcafe"
//
// WIFI_HOSTNAME: You probably don't need to change this
#define WIFI_HOSTNAME "dccex"
//
/////////////////////////////////////////////////////////////////////////////////////
//
// Wifi connect timeout in milliseconds. Default is 14000 (14 seconds). You only need
// to set this if you have an extremely slow Wifi router.
//
#define WIFI_CONNECT_TIMEOUT 14000
/////////////////////////////////////////////////////////////////////////////////////
//
// ENABLE_ETHERNET: Set to true if you have an Arduino Ethernet card (wired). This
// is not for Wifi. You will then need the Arduino Ethernet library as well
//
//#define ENABLE_ETHERNET true
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE STATIC IP ADDRESS *OR* COMMENT OUT TO USE DHCP
//
//#define IP_ADDRESS { 192, 168, 1, 31 }
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE MAC ADDRESS ARRAY FOR ETHERNET COMMUNICATIONS INTERFACE
//
// Uncomment to use with Ethernet Shields
//
// Ethernet Shields do not have have a MAC address in hardware. There may be one on
// a sticker on the Shield that you should use. Otherwise choose one of the ones below
// Be certain that no other device on your network has this same MAC address!
//
// 52:b8:8a:8e:ce:21
// e3:e9:73:e1:db:0d
// 54:2b:13:52:ac:0c
// NOTE: This is not used with ESP8266 WiFi modules.
//#define MAC_ADDRESS { 0x52, 0xB8, 0x8A, 0x8E, 0xCE, 0x21 } // MAC address of your networking card found on the sticker on your card or take one from above
//
// #define MAC_ADDRESS { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xEF }
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE LCD SCREEN USAGE BY THE BASE STATION
//
// Note: This feature requires an I2C enabled LCD screen using a Hitachi HD44780
// controller and a PCF8574 based I2C 'backpack',
// OR an I2C Oled screen based on SSD1306 (128x64 or 128x32) controller,
// OR an I2C Oled screen based on SH1106 (132x64) controller.
// To enable, uncomment one of the lines below
// define LCD_DRIVER for I2C LCD address 0x3f,16 cols, 2 rows
//#define LCD_DRIVER {SubBus_4,0x27},20,4
//OR define OLED_DRIVER width,height in pixels (address auto detected)
#if defined(ARDUINO_ARCH_STM32)
#define OLED_DRIVER 0x3c, 128, 64
#else
#define OLED_DRIVER {SubBus_0,0x3c}, 128, 32
#endif
#define SCROLLMODE 1
/////////////////////////////////////////////////////////////////////////////////////
// DISABLE EEPROM
//
// If you do not need the EEPROM at all, you can disable all the code that saves
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
// to do that. Of course, then none of the EEPROM related commands work.
//
#define DISABLE_EEPROM
/////////////////////////////////////////////////////////////////////////////////////
//
// DEFINE TURNOUTS/ACCESSORIES FOLLOW NORM RCN-213
//
// According to norm RCN-213 a DCC packet with a 1 is closed/straight
// and one with a 0 is thrown/diverging. In DCC++ Classic, and in previous
// versions of DCC++EX, a turnout throw command was implemented in the packet as
// '1' and a close command as '0'. The #define below makes the states
// match with the norm. But we don't want to cause havoc on existent layouts,
// so we define this only for new installations. If you don't want this,
// don't add it to your config.h.
//#define DCC_TURNOUTS_RCN_213
// The following #define likewise inverts the behaviour of the <a> command
// for triggering DCC Accessory Decoders, so that <a addr subaddr 0> generates a
// DCC packet with D=1 (close turnout) and <a addr subaddr 1> generates D=0
// (throw turnout).
//#define DCC_ACCESSORY_RCN_213
/////////////////////////////////////////////////////////////////////////////////////

View File

@@ -148,6 +148,7 @@
#define I2C_USE_WIRE
#endif
/* TODO when ready
#elif defined(ARDUINO_ARCH_RP2040)
#define ARDUINO_TYPE "RP2040"
@@ -182,15 +183,6 @@
#define WIFI_ON false
#endif
#ifndef WIFI_FORCE_AP
#define WIFI_FORCE_AP false
#else
#if WIFI_FORCE_AP==true || WIFI_FORCE_AP==false
#else
#error WIFI_FORCE_AP needs to be true or false
#endif
#endif
#if ENABLE_ETHERNET
#if defined(HAS_ENOUGH_MEMORY)
#define ETHERNET_ON true
@@ -214,7 +206,7 @@
#define WIFI_SERIAL_LINK_SPEED 115200
#if __has_include ( "myAutomation.h")
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
#define EXRAIL_ACTIVE
#else
#define EXRAIL_WARNING

View File

@@ -1,13 +0,0 @@
@ECHO OFF
FOR /f "tokens=*" %%a IN ('powershell Get-ExecutionPolicy -Scope CurrentUser') DO SET PS_POLICY=%%a
IF NOT %PS_POLICY=="Bypass" (
powershell Set-ExecutionPolicy -Scope CurrentUser Bypass
)
powershell %~dp0%installer.ps1
IF NOT %PS_POLICY=="Bypass" (
powershell Set-ExecutionPolicy -Scope CurrentUser %PS_POLICY%
)

View File

@@ -1,540 +0,0 @@
<#
# © 2023 Peter Cole
#
# This file is part of EX-CommandStation
#
# This is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# It is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
#>
<############################################
For script errors set ExecutionPolicy:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass
############################################>
<############################################
Optional command line parameters:
$buildDirectory - specify an existing directory rather than generating a new unique one
$configDirectory - specify a directory containing existing files as per $configFiles
############################################>
Param(
[Parameter()]
[String]$buildDirectory,
[Parameter()]
[String]$configDirectory
)
<############################################
Define global parameters here such as known URLs etc.
############################################>
$installerVersion = "v0.0.8"
$configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h")
$wifiBoards = @("arduino:avr:mega", "esp32:esp32:esp32")
$userDirectory = $env:USERPROFILE + "\"
$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags"
$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/"
if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") {
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip"
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_64bit.zip"
} else {
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip"
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_32bit.zip"
}
$arduinoCLIDirectory = $userDirectory + "arduino-cli"
$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe"
<############################################
List of supported devices with FQBN in case clones used that aren't detected
############################################>
$supportedDevices = @(
@{
name = "Arduino Mega or Mega 2560"
fqbn = "arduino:avr:mega"
},
@{
name = "Arduino Nano"
fqbn = "arduino:avr:nano"
},
@{
name = "Arduino Uno"
fqbn = "arduino:avr:uno"
},
@{
name = "ESP32 Dev Module"
fqbn = "esp32:esp32:esp32"
}
)
<############################################
List of supported displays
############################################>
$displayList = @(
@{
option = "LCD 16 columns x 2 rows"
configLine = "#define LCD_DRIVER 0x27,16,2"
},
@{
option = "LCD 16 columns x 4 rows"
configLine = "#define LCD_DRIVER 0x27,16,4"
},
@{
option = "OLED 128 x 32"
configLine = "#define OLED_DRIVER 128,32"
},
@{
option = "OLED 128 x 64"
configLine = "#define OLED_DRIVER 128,64"
}
)
<############################################
Basics of config.h
############################################>
$configLines = @(
"/*",
"This config.h file was generated by the DCC-EX PowerShell installer $installerVersion",
"*/",
"",
"// Define standard motor shield",
"#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD",
""
)
<############################################
Set default action for progress indicators, warnings, and errors
############################################>
$global:ProgressPreference = "SilentlyContinue"
$global:WarningPreference = "SilentlyContinue"
$global:ErrorActionPreference = "SilentlyContinue"
<############################################
If $buildDirectory not provided, generate a new time/date stamp based directory to use
############################################>
if (!$PSBoundParameters.ContainsKey('buildDirectory')) {
$buildDate = Get-Date -Format 'yyyyMMdd-HHmmss'
$buildDirectory = $userDirectory + "EX-CommandStation-Installer\" + $buildDate
}
$commandStationDirectory = $buildDirectory + "\CommandStation-EX"
<############################################
Write out intro message and prompt to continue
############################################>
@"
Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVersion)
Current installer options:
- EX-CommandStation will be built in $commandStationDirectory
- Arduino CLI will downloaded and extracted to $arduinoCLIDirectory
Before continuing, please ensure:
- Your computer is connected to the internet
- The device you wish to install EX-CommandStation on is connected to a USB port
This installer will obtain the Arduino CLI (if not already present), and then download and install your chosen version of EX-CommandStation
"@
<############################################
Prompt user to confirm all is ready to proceed
############################################>
$confirmation = Read-Host "Enter 'Y' or 'y' then press <Enter> to confirm you are ready to proceed, any other key to exit"
if ($confirmation -ne "Y" -and $confirmation -ne "y") {
Exit
}
<############################################
See if we have the Arduino CLI already, otherwise download and extract it
############################################>
if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) {
if (!(Test-Path -PathType Container -Path $arduinoCLIDirectory)) {
try {
New-Item -ItemType Directory -Path $arduinoCLIDirectory | Out-Null
}
catch {
Write-Output "Arduino CLI does not exist and cannot create directory $arduinoCLIDirectory"
Exit
}
}
Write-Output "`r`nDownloading and extracting Arduino CLI"
try {
Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip
}
catch {
Write-Output "Failed to download Arduino CLI"
Exit
}
try {
Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force
}
catch {
Write-Output "Failed to extract Arduino CLI"
}
} else {
Write-Output "`r`nArduino CLI already downloaded, ensuring it is up to date and you have a board connected"
}
<############################################
Make sure Arduino CLI core index updated and list of boards populated
############################################>
# Need to do an initial board list to download everything first
try {
& $arduinoCLI core update-index | Out-Null
}
catch {
Write-Output "Failed to update Arduino CLI core index"
Exit
}
# Need to do an initial board list to download everything first
try {
& $arduinoCLI board list | Out-Null
}
catch {
Write-Output "Failed to update Arduino CLI board list"
Exit
}
<############################################
Identify available board(s)
############################################>
try {
$boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json
}
catch {
Write-Output "Failed to obtain list of boards"
Exit
}
<############################################
Get user to select board
############################################>
if ($boardList.count -eq 0) {
Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it"
Exit
} else {
@"
Devices attached to COM ports:
------------------------------
"@
$boardSelect = 1
foreach ($board in $boardList) {
if ($board.matching_boards.name) {
$boardName = $board.matching_boards.name
} else {
$boardName = "Unknown device"
}
$port = $board.port.address
Write-Output "$boardSelect - $boardName on port $port"
$boardSelect++
}
Write-Output "$boardSelect - Exit"
$userSelection = 0
do {
[int]$userSelection = Read-Host "`r`nSelect the device to use from the list above"
} until (
(($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1)))
)
if ($userSelection -eq ($boardList.count + 1)) {
Write-Output "Exiting installer"
Exit
} else {
$selectedBoard = $userSelection - 1
}
}
<############################################
If the board is unknown, need to choose which one
############################################>
if ($null -eq $boardList[$selectedBoard].matching_boards.name) {
Write-Output "The device selected is unknown, these boards are supported:`r`n"
$deviceSelect = 1
foreach ($device in $supportedDevices) {
Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)"
$deviceSelect++
}
Write-Output "$deviceSelect - Exit"
$userSelection = 0
do {
[int]$userSelection = Read-Host "Select the board type from the list above"
} until (
(($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1)))
)
if ($userSelection -eq ($supportedDevices.count + 1)) {
Write-Output "Exiting installer"
Exit
} else {
$deviceName = $supportedDevices[$userSelection - 1].name
$deviceFQBN = $supportedDevices[$userSelection - 1].fqbn
$devicePort = $boardList[$selectedBoard].port.address
}
} else {
$deviceName = $boardList[$selectedBoard].matching_boards.name
$deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn
$devicePort = $boardList[$selectedBoard].port.address
}
<############################################
Get the list of tags
############################################>
try {
$gitHubTags = Invoke-RestMethod -Uri $gitHubAPITags
}
catch {
Write-Output "Failed to obtain list of available EX-CommandStation versions"
Exit
}
<############################################
Get our GitHub tag list in a hash so we can sort by version numbers and extract just the ones we want
############################################>
$versionMatch = ".*?v(\d+)\.(\d+).(\d+)-(.*)"
$tagList = @{}
foreach ($tag in $gitHubTags) {
$tagHash = @{}
$tagHash["Ref"] = $tag.ref
$version = $tag.ref.split("/")[2]
$null = $version -match $versionMatch
$tagHash["Major"] = [int]$Matches[1]
$tagHash["Minor"] = [int]$Matches[2]
$tagHash["Patch"] = [int]$Matches[3]
$tagHash["Type"] = $Matches[4]
$tagList.Add($version, $tagHash)
}
<############################################
Get latest two Prod and Devel for user to select
############################################>
$userList = @{}
$prodCount = 1
$devCount = 1
$select = 1
foreach ($tag in $tagList.Keys | Sort-Object {$tagList[$_]["Major"]},{$tagList[$_]["Minor"]},{$tagList[$_]["Patch"]} -Descending) {
if (($tagList[$tag]["Type"] -eq "Prod") -and $prodCount -le 2) {
$userList[$select] = $tag
$select++
$prodCount++
} elseif (($tagList[$tag]["Type"] -eq "Devel") -and $devCount -le 2) {
$userList[$select] = $tag
$select++
$devCount++
}
}
<############################################
Display options for user to select and get the selection
############################################>
@"
Available EX-CommandStation versions:
-------------------------------------
"@
foreach ($selection in $userList.Keys | Sort-Object $selection) {
Write-Output "$selection - $($userList[$selection])"
}
Write-Output "5 - Exit"
$userSelection = 0
do {
[int]$userSelection = Read-Host "`r`nSelect the version to install from the list above (1 - 5)"
} until (
(($userSelection -ge 1) -and ($userSelection -le 5))
)
if ($userSelection -eq 5) {
Write-Output "Exiting installer"
Exit
} else {
$downloadURL = $gitHubURLPrefix + $tagList[$userList[$userSelection]]["Ref"] + ".zip"
}
<############################################
Create build directory if it doesn't exist, or fail
############################################>
if (!(Test-Path -PathType Container -Path $buildDirectory)) {
try {
New-Item -ItemType Directory -Path $buildDirectory | Out-Null
}
catch {
Write-Output "Could not create build directory $buildDirectory"
Exit
}
}
<############################################
Download the chosen version to the build directory
############################################>
$downladFile = $buildDirectory + "\CommandStation-EX.zip"
Write-Output "Downloading and extracting $($userList[$userSelection])"
try {
Invoke-WebRequest -Uri $downloadURL -OutFile $downladFile
}
catch {
Write-Output "Error downloading EX-CommandStation zip file"
Exit
}
<############################################
If folder exists, bail out and tell user
############################################>
if (Test-Path -PathType Container -Path "$buildDirectory\CommandStation-EX") {
Write-Output "EX-CommandStation directory already exists, please ensure you have copied any user files then delete manually: $buildDirectory\CommandStation-EX"
Exit
}
<############################################
Extract and rename to CommandStation-EX to allow building
############################################>
try {
Expand-Archive -Path $downladFile -DestinationPath $buildDirectory -Force
}
catch {
Write-Output "Failed to extract EX-CommandStation zip file"
Exit
}
$folderName = $buildDirectory + "\CommandStation-EX-" + ($userList[$userSelection] -replace "^v", "")
try {
Rename-Item -Path $folderName -NewName $commandStationDirectory
}
catch {
Write-Output "Could not rename folder"
Exit
}
<############################################
If config directory provided, copy files here
############################################>
if ($PSBoundParameters.ContainsKey('configDirectory')) {
if (Test-Path -PathType Container -Path $configDirectory) {
foreach ($file in $configFiles) {
if (Test-Path -PathType Leaf -Path "$configDirectory\$file") {
Copy-Item -Path "$configDirectory\$file" -Destination "$commandStationDirectory\$file"
}
}
} else {
Write-Output "User provided configuration directory $configDirectory does not exist, skipping"
}
} else {
<############################################
If no config directory provided, prompt for display option
############################################>
Write-Output "`r`nIf you have an LCD or OLED display connected, you can configure it here`r`n"
Write-Output "1 - I have no display, skip this step"
$displaySelect = 2
foreach ($display in $displayList) {
Write-Output "$displaySelect - $($displayList[$displaySelect - 2].option)"
$displaySelect++
}
Write-Output "$($displayList.Count + 2) - Exit"
do {
[int]$displayChoice = Read-Host "`r`nSelect a display option"
} until (
($displayChoice -ge 1 -and $displayChoice -le ($displayList.Count + 2))
)
if ($displayChoice -eq ($displayList.Count + 2)) {
Exit
} elseif ($displayChoice -ge 2) {
$configLines+= "// Display configuration"
$configLines+= "$($displayList[$displayChoice - 2].configLine)"
$configLines+= "#define SCROLLMODE 1 // Alternate between pages"
}
<############################################
If device supports WiFi, prompt to configure
############################################>
if ($wifiBoards.Contains($deviceFQBN)) {
Write-Output "`r`nYour chosen board supports WiFi`r`n"
Write-Output "1 - I don't want WiFi, skip this step
2 - Configure my device as an access point I will connect to directly
3 - Configure my device to connect to my home WiFi network
4 - Exit"
do {
[int]$wifiChoice = Read-Host "`r`nSelect a WiFi option"
} until (
($wifiChoice -ge 1 -and $wifiChoice -le 4)
)
if ($wifiChoice -eq 4) {
Exit
} elseif ($wifiChoice -ne 1) {
$configLines+= ""
$configLines+= "// WiFi configuration"
$configLines+= "#define ENABLE_WIFI true"
$configLines+= "#define IP_PORT 2560"
$configLines+= "#define WIFI_HOSTNAME ""dccex"""
$configLines+= "#define WIFI_CHANNEL 1"
if ($wifiChoice -eq 2) {
$configLines+= "#define WIFI_SSID ""Your network name"""
$configLines+= "#define WIFI_PASSWORD ""Your network passwd"""
}
if ($wifiChoice -eq 3) {
$wifiSSID = Read-Host "Please enter the SSID of your home network here"
$wifiPassword = Read-Host "Please enter your home network WiFi password here"
$configLines+= "#define WIFI_SSID ""$($wifiSSID)"""
$configLines+= "#define WIFI_PASSWORD ""$($wifiPassword)"""
}
}
}
<############################################
Write out config.h to a file here only if config directory not provided
############################################>
$configH = $commandStationDirectory + "\config.h"
try {
$configLines | Out-File -FilePath $configH -Encoding ascii
}
catch {
Write-Output "Error writing config file to $configH"
Exit
}
}
<############################################
Install core libraries for the platform
############################################>
$platformArray = $deviceFQBN.split(":")
$platform = $platformArray[0] + ":" + $platformArray[1]
try {
& $arduinoCLI core install $platform
}
catch {
Write-Output "Error install core libraries"
Exit
}
<############################################
Upload the sketch to the selected board
############################################>
#$arduinoCLI upload -b fqbn -p port $commandStationDirectory
Write-Output "Compiling and uploading to $deviceName on $devicePort"
try {
$output = & $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json
}
catch {
Write-Output "Failed to compile"
Exit
}
if ($output.success -eq "True") {
Write-Output "`r`nCongratulations! DCC-EX EX-CommandStation $($userList[$userSelection]) has been installed on your $deviceName`r`n"
} else {
Write-Output "`r`nThere was an error installing $($userList[$userSelection]) on your $($deviceName), please take note of the errors provided:`r`n"
if ($null -ne $output.compiler_err) {
Write-Output "Compiler error: $($output.compiler_err)`r`n"
}
if ($null -ne $output.builder_result) {
Write-Output "Builder result: $($output.builder_result)`r`n"
}
}
Write-Output "`r`nPress any key to exit the installer"
[void][System.Console]::ReadKey($true)

View File

@@ -1,7 +1,7 @@
#!/bin/bash
#
# © 2022,2023 Harald Barth
# © 2022 Harald Barth
#
# This file is part of CommandStation-EX
#
@@ -29,33 +29,14 @@ ACLI="./bin/arduino-cli"
function need () {
type -p $1 > /dev/null && return
dpkg -l $1 2>&1 | egrep ^ii >/dev/null && return
sudo apt-get install $1
type -p $1 > /dev/null && return
echo "Could not install $1, abort"
exit 255
}
need git
if cat /etc/issue | egrep '^Raspbian' 2>&1 >/dev/null ; then
# we are on a raspi where we do not support graphical
unset DISPLAY
fi
if [ x$DISPLAY != x ] ; then
# we have DISPLAY, do the graphic thing
need python3-tk
need python3.8-venv
mkdir -p ~/ex-installer/venv
python3 -m venv ~/ex-installer/venv
cd ~/ex-installer/venv || exit 255
source ./bin/activate
git clone https://github.com/DCC-EX/EX-Installer
cd EX-Installer || exit 255
pip3 install -r requirements.txt
exec python3 -m ex_installer
fi
if test -d `basename "$DCCEXGITURL"` ; then
: assume we are almost there
cd `basename "$DCCEXGITURL"` || exit 255
@@ -88,10 +69,10 @@ else
# need to do this config better
cp -p config.example.h config.h
fi
need curl
if test -x "$ACLI" ; then
: all well
else
need curl
curl "$ACLIINSTALL" > acliinstall.sh
chmod +x acliinstall.sh
./acliinstall.sh

View File

@@ -24,7 +24,6 @@
//#include "IO_TouchKeypad.h // Touch keypad with 16 keys
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
//#include "IO_EXFastClock.h" // FastClock driver
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
//==========================================================================
// The function halSetup() is invoked from CS if it exists within the build.
@@ -52,7 +51,7 @@ void halSetup() {
// Create a 20x4 LCD display device as display number 2
// (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")').
// HALDisplay<LiquidCrystal>::create(2, 0x27, 20, 4);
// HALDisplay<LiquidCrystal>(2, 0x27, 20, 4);
//=======================================================================

View File

@@ -20,11 +20,11 @@ default_envs =
ESP32
Nucleo-F411RE
Nucleo-F446RE
Teensy3_2
Teensy3_5
Teensy3_6
Teensy4_0
Teensy4_1
Teensy3.2
Teensy3.5
Teensy3.6
Teensy4.0
Teensy4.1
src_dir = .
include_dir = .
@@ -53,7 +53,7 @@ monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
[env:Arduino-M0]
[env:Arduino M0]
platform = atmelsam
board = mzeroUSB
framework = arduino
@@ -173,8 +173,6 @@ board = esp32dev
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17
monitor_speed = 115200
monitor_echo = yes
[env:Nucleo-F411RE]
platform = ststm32
@@ -190,11 +188,11 @@ platform = ststm32
board = nucleo_f446re
framework = arduino
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 -DDIAG_LOOPTIMES ; -DDIAG_IO
monitor_speed = 115200
monitor_echo = yes
[env:Teensy3_2]
[env:Teensy3.2]
platform = teensy
board = teensy31
framework = arduino
@@ -202,7 +200,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy3_5]
[env:Teensy3.5]
platform = teensy
board = teensy35
framework = arduino
@@ -210,7 +208,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy3_6]
[env:Teensy3.6]
platform = teensy
board = teensy36
framework = arduino
@@ -218,7 +216,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy4_0]
[env:Teensy4.0]
platform = teensy
board = teensy40
framework = arduino
@@ -226,7 +224,7 @@ build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore = NativeEthernet
[env:Teensy4_1]
[env:Teensy4.1]
platform = teensy
board = teensy41
framework = arduino

View File

@@ -3,58 +3,8 @@
#include "StringFormatter.h"
#define VERSION "5.0.7"
// 5.0.7 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
// 5.0.6 - Bugfix lost TURNOUTL description
// 5.0.5 - Bugfix version detection logic and better message
// 5.0.4 - Bugfix: <JR> misses default roster.
// 5.0.3 - Check bad AT firmware version
// 5.0.2 - Bugfix: ESP32 30ms off time
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
// 5.0.0 - Make 4.2.69 the 5.0.0 release
// 4.2.69 - Bugfix: Make <!> work in DC mode
// 4.2.68 - Rename track mode OFF to NONE
// 4.2.67 - AVR: Pin specific timer register seting
// - Protect Uno user from choosing DC(X)
// - More Nucleo variant defines
// - GPIO PCA9555 / TCA9555 support
// 4.2.66 - Throttle inrush current by applying PWM to brake pin when
// fault pin goes active
// 4.2.65 - new config WIFI_FORCE_AP option
// 4.2.63 - completely new overcurrent detection
// - ESP32 protect from race in RMT code
// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position
// - Update IO_EXTurntable.h to remove forced I2C clock speed
// - Show device offline if EX-Turntable not connected
// 4.2.61 - MAX_CURRENT restriction (caps motor shield value)
// 4.2.60 - Add mDNS capability to ESP32 for autodiscovery
// 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_
// 4.2.58 - Start motordriver as soon as possible but without waveform
// 4.2.57 - New overload handling (faster and handles commonFaultPin again)
// - Optimize analog read STM32
// 4.2.56 - Update IO_RotaryEncoder.h:
// - Improved I2C communication, non-blocking reads
// - Enable sending positions to the encoder from EXRAIL via SERVO()
// 4.2.55 - Optimize analog read for AVR
// 4.2.54 - EX8874 shield in config.example.h
// - Fix: Better warnings for pin number errors
// - Fix: Default roster list possible in Withrottle and <jR>
// - Fix: Pin handling supports pins up to 254
// 4.2.53 - Fix: Fault pin handling made more straight forward
// 4.2.52 - Experimental support for sabertooth motor controller on ESP32
// 4.2.51 - Add DISABLE_PROG to disable programming to save RAM/Flash
// 4.2.50 - Fixes: estop all, turnout eeprom, cab ID check
// 4.2.49 - Exrail SPEED take notice of external direction change
// 4.2.48 - BROADCAST/WITHROTTLE Exrail macros
// 4.2.47 - Correct response to <JA 0>
// 4.2.46 - Support boards with inverted fault pin
// 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events
// 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe
// 4.2.43 - Fix STM32 set right port mode bits for analog
// 4.2.42 - Added EXRAIL TURNOUTL Macro definition
// 4.2.41 - Move HAl startup to ASAP in setup()
// - Fix DNOU8 output pin setup to all LOW
// 4.2.40 - Automatically detect conflicting default I2C devices and disable
#define VERSION "4.2.39"
// 4.2.39 - DFplayer driver now polls device to detect failures and errors.
// 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included
// 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL;
@@ -119,10 +69,6 @@
// 4.2.11 Exrail IFLOCO feature added
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
// IO_EXIOExpander.h input speed optimisation
// ONCLOCK and ONCLOCKTIME command added to EXRAIL for EX-FastCLock
// <JC> Serial command added for EX-FastClock
// <jC> Broadcast added for EX-FastClock
// IO_EXFastClock.h added for I2C FastClock connection
// 4.2.9 duinoNodes support
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
// Withrottle connect/disconnect improvements