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

Compare commits

..

2 Commits

Author SHA1 Message Date
FrightRisk
041c9bc045 add monitor commands to csb1 branch for the esp32 2024-07-16 08:45:39 -04:00
FrightRisk
156a29a629 Add EX-CSB1 definitions 2024-05-24 08:46:11 -04:00
23 changed files with 220 additions and 594 deletions

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
@@ -96,11 +95,11 @@ 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

View File

@@ -219,9 +219,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
@@ -490,9 +487,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#endif
else break; // will reply <X>
}
TrackManager::setJoin(join);
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(join);
CommandDistributor::broadcastPower();
return;
@@ -519,12 +516,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
else break; // will reply <X>
}
TrackManager::setJoin(false);
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF);
}
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
return;

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

@@ -180,8 +180,8 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcSetup(cnt_channel, 1000, 8);
ledcAttachPin(pin, cnt_channel);
ledcSetup(cnt_channel, 1000, 8);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
}

View File

@@ -35,7 +35,7 @@
#endif
#include "DIAG.h"
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
#if defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have additional serial ports defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
@@ -52,7 +52,7 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
#elif defined(ARDUINO_NUCLEO_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

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);
{

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202308012308Z"
#define GITHUB_SHA "devel-202306222129Z"

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

@@ -134,13 +134,12 @@ private:
}
}
// 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;
@@ -149,12 +148,9 @@ private:
}
}
// Send a position update to the rotary encoder software
// To be valid, must be 0 to 255, and different to the current position
// If the current position is the same, it was initiated by the rotary encoder
void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override {
if (vpin == _firstVpin + 2) {
if (position >= 0 && position <= 255 && position != _position) {
if (position >= 0 && position <= 255) {
byte newPosition = position & 0xFF;
byte _positionBuffer[2] = {RE_MOVE, newPosition};
I2CManager.write(_I2CAddress, _positionBuffer, 2);

View File

@@ -135,11 +135,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
// float calculations or libraray code.
senseFactorInternal=sense_factor * senseScale;
tripMilliamps=trip_milliamps;
#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
@@ -173,11 +169,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 +209,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
@@ -278,7 +270,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 +278,17 @@ 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
TCCR5B = (TCCR5B & B11111000) | B00000100; // same for timer 5 which is like timer 4
#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,13 +296,11 @@ void MotorDriver::setDCSignal(byte speedcode) {
#if defined(ARDUINO_ARCH_ESP32)
{
int f = 131;
#ifdef VARIABLE_TONES
if (tSpeed > 2) {
if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ;
}
}
#endif
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
}
#endif
@@ -376,60 +339,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,169 +368,112 @@ 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) {
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
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);
} else {
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
case POWERMODE::OFF:
if (overloadNow) {
// reset overload condition as we have just turned off power
// DIAG(F("OVERLOAD POFF OFF"));
overloadNow=false;
setLastPowerChange();
}
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;
if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
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);
case POWERMODE::ON:
// Check current
lastCurrent=getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
if (!overloadNow) {
// turn on overload condition as fault pin has gone active
// DIAG(F("OVERLOAD FPIN ON"));
overloadNow=true;
setLastPowerChangeOverload();
}
lastCurrent = -lastCurrent;
{
if (lastCurrent < tripValue) {
if (power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) && // almost virgin
microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_LOW) {
// Ignore 50ms fault pin if no current
DIAG(F("TRACK %c FAULT PIN (50ms ignore)"), trackno + 'A');
break;
}
lastCurrent = tripValue; // exaggerate so condition below (*) is true
} else {
if (power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && // virgin
microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_HIGH) {
// Ignore 5ms fault pin if we see current
DIAG(F("TRACK %c FAULT PIN (5ms ignore)"), trackno + 'A');
break;
}
}
DIAG(F("TRACK %c FAULT PIN"), trackno + 'A');
}
}
// // //
// above we looked at fault pin, below we look at current
// // //
if (lastCurrent < tripValue) { // see above (*)
if (overloadNow) {
// current is below trip value, turn off overload condition
// DIAG(F("OVERLOAD PON OFF"));
overloadNow=false;
setLastPowerChange();
}
if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
} else {
// too much current
if (!overloadNow) {
// current is over trip value, turn on overload condition
// DIAG(F("OVERLOAD PON ON"));
overloadNow=true;
setLastPowerChange();
}
unsigned long uSecs = microsSinceLastPowerChange();
if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin
uSecs > POWER_SAMPLE_OFF_DELAY) {
// Overload has existed longer than delay (typ. 10ms)
setPower(POWERMODE::OVERLOAD);
if (overloadNow) {
// the setPower just turned off, so overload is now gone
// DIAG(F("OVERLOAD PON OFF"));
overloadNow=false;
setLastPowerChangeOverload();
}
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, uSecs, power_sample_overload_wait);
}
break;
}
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
}
// all well
unsigned long goodtime = micros() - lastBadSample;
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
if (true || notFromOverload) { // we did a RESTORE message XXX
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
}
throttleInrush(false);
setPower(POWERMODE::ON);
}
break;
}
case POWERMODE::OVERLOAD: {
lastPowerMode = POWERMODE::OVERLOAD;
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
case POWERMODE::OVERLOAD:
{
// Try setting it back on after the OVERLOAD_WAIT
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange());
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
setPower(POWERMODE::ON);
// here we change power but not the overloadNow as that was
// already changed to false when we entered POWERMODE::OVERLOAD
// so we need to set the lastPowerChange anyway.
overloadNow=false;
setLastPowerChange();
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
setPower(POWERMODE::ALERT);
}
break;
}
break;
default:
break;
}

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_OFF = 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)
@@ -111,7 +107,7 @@ 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:
@@ -149,7 +145,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);
@@ -197,43 +192,35 @@ class MotorDriver {
// 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) {
inline unsigned long microsSinceLastPowerChange() {
unsigned long now = micros();
unsigned long diff = now - lastPowerChange[(int)mode];
unsigned long diff = now - lastPowerChange;
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
lastPowerChange = now - 30000000UL; // 30 seconds ago
return diff;
};
inline void setLastPowerChange() {
lastPowerChange = micros();
};
// as setLastPowerChange but sets the global timestamp as well which
// is only used to sync power restore in case of common Fault pin.
inline void setLastPowerChangeOverload() {
if (commonFaultPin)
globalOverloadStart = lastPowerChange = micros();
else
setLastPowerChange();
};
#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;
@@ -254,14 +241,12 @@ class MotorDriver {
int rawCurrentTripValue;
// current sampling
POWERMODE powerMode;
POWERMODE lastPowerMode;
unsigned long lastPowerChange[4]; // timestamp in microseconds
unsigned long lastBadSample; // timestamp in microseconds
bool overloadNow = false;
unsigned long lastPowerChange; // timestamp in microseconds
// used to sync restore time when common Fault pin detected
static unsigned long globalOverloadStart; // timestamp in microseconds
int progTripValue;
int lastCurrent; //temp value
int tripValue; //temp value
int lastCurrent;
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
@@ -271,17 +256,15 @@ class MotorDriver {
// 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;
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 100UL;
// 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;
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 50000UL;
// 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;
static const unsigned long POWER_SAMPLE_OFF_DELAY = 10000UL;
// Upper limit for retry period
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
@@ -290,7 +273,6 @@ class MotorDriver {
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_OFF; // we assume off at startup
};
#endif

View File

@@ -89,10 +89,27 @@
// 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"),\
#define EX8874_SHIELD F("EX-8874"),\
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*/)
// EX-CSB1 motor shield definition - note it is different from ESPduino32 pins!
#define EX_CSB1 F("EX-CSB1"),\
new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 1.27, 5000, 19), \
new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 1.27, 5000, 23)
// EX-CSB1 with EX-8874 stacked on top for 4 outputs
#define EX_CSB1_STACK F("EX-CSB1 Stacked"),\
new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 1.27, 5000, 19), \
new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 1.27, 5000, 23), \
new MotorDriver(26, 5, UNUSED_PIN, 13, 36, 1.27, 5000, 18), \
new MotorDriver(16, 4, UNUSED_PIN, 12, 39, 1.27, 5000, 17)
// BOOSTER PIN INPUT ON ESP32
// On ESP32 you have the possibility to define a pin as booster input
// Arduino pin D2 is GPIO 26 on ESPDuino32, and GPIO 32 on EX-CSB1
#define BOOSTER_INPUT 32
#else
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \

View File

@@ -31,7 +31,7 @@
#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;
@@ -44,6 +44,7 @@ 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;
@@ -73,7 +74,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.
@@ -84,7 +85,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;
@@ -137,10 +138,10 @@ void TrackManager::Setup(const FSH * 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_OFF);
track[t]->setTrackLetter('A'+t);
lastTrack=t;
}
@@ -182,26 +183,21 @@ void TrackManager::setPROGSignal( bool on) {
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
FOR_EACH_TRACK(t) {
if (trackDCAddr[t]!=cab) 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 (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
@@ -222,9 +218,9 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
#endif
// only allow 1 track to be prog
FOR_EACH_TRACK(t)
if (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_OFF);
trackMode[t]=TRACK_MODE_OFF;
track[t]->makeProgTrack(false); // revoke prog track special handling
streamTrackState(NULL,t);
}
@@ -232,7 +228,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);
@@ -259,7 +255,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
@@ -267,7 +263,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;
@@ -305,7 +301,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);
}
@@ -351,7 +347,7 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
// null stream means send to commandDistributor for broadcast
if (track[t]==NULL) return;
auto format=F("");
switch(track[t]->getMode()) {
switch(trackMode[t]) {
case TRACK_MODE_MAIN:
format=F("<= %c MAIN>\n");
break;
@@ -391,13 +387,13 @@ void TrackManager::loop() {
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;
}
@@ -405,7 +401,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
@@ -415,7 +411,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
@@ -451,8 +447,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;
}
@@ -496,7 +492,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;

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;
@@ -96,6 +100,7 @@ class TrackManager {
static POWERMODE mainPowerGuess;
static void applyDCSpeed(byte t);
static TRACK_MODE trackMode[MAX_TRACKS];
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
#ifdef ARDUINO_ARCH_ESP32
static byte tempProgTrack; // holds the prog track number during join

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("DCCEX_");
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
@@ -60,7 +59,7 @@ Stream * WifiInterface::wifiStream;
#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)
#if defined(ARDUINO_NUCLEO_F411RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial6
@@ -68,11 +67,9 @@ Stream * WifiInterface::wifiStream;
#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)
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#define NUM_SERIAL 2
#define SERIAL1 Serial6
#else
#warning This variant of Nucleo not yet explicitly supported
#endif
#endif
@@ -86,8 +83,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;
@@ -99,13 +95,12 @@ 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);
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
@@ -115,7 +110,7 @@ bool WifiInterface::setup(long serial_link_speed,
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
#endif
@@ -126,7 +121,7 @@ bool WifiInterface::setup(long serial_link_speed,
if (wifiUp == WIFI_NOAT)
{
SERIAL3.begin(serial_link_speed);
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
@@ -144,7 +139,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++;
@@ -153,7 +148,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"));
@@ -177,7 +172,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;
@@ -230,7 +225,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
@@ -290,19 +285,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

@@ -4,7 +4,6 @@
* © 2020-2023 Harald Barth
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2023 Nathan Kellenicki
*
* This file is part of CommandStation-EX
*
@@ -58,21 +57,6 @@ The configuration file for DCC-EX Command Station
// +-----------------------v
//
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
//
/////////////////////////////////////////////////////////////////////////////////////
//
// If you want to restrict the maximum current LOWER than what your
// motor shield can provide, you can do that here. For example if you
// have a motor shield that can provide 5A and your power supply can
// only provide 2.5A then you should restict the maximum current to
// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before
// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example
// it would be 2250, adjust the number according to your PS. If your
// PS has a higher rating than your motor shield you do not need this.
// You can use this as well if you are cautious and your trains do not
// need full current.
// #define MAX_CURRENT 2250
//
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
@@ -124,11 +108,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
/////////////////////////////////////////////////////////////////////////////////////
//

View File

@@ -182,15 +182,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 +205,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,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

View File

@@ -3,21 +3,8 @@
#include "StringFormatter.h"
#define VERSION "4.2.67"
// 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
#define VERSION "4.2.59"
// 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)