1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 02:13:45 +02:00

Compare commits

...

32 Commits

Author SHA1 Message Date
Harald Barth
4192c1f5a3 Do not invoke graphical install on Raspbian 2023-07-06 16:58:36 +02:00
Harald Barth
c2fcdddd1f ESP32 protect from race in RMT code 2023-07-06 15:19:44 +02:00
Harald Barth
f19db3aa5c DISABLE_PROG does count as less RAM as well 2023-07-04 16:25:15 +02:00
Harald Barth
e6a40e622c download graphic installer if DISPLAY 2023-07-03 23:43:21 +02:00
Harald Barth
96a46f36c2 Adjust overcurrent timeouts 2023-07-03 00:21:52 +02:00
Harald Barth
10c59028e1 Add documentation 2023-07-02 20:33:29 +02:00
Harald Barth
ab1356d070 Change first join/unjoin and set power after that 2023-07-02 13:55:56 +02:00
Harald Barth
70d4c016ef completely new overcurrent detection 2023-07-02 01:33:41 +02:00
Harald Barth
7c41ec7c25 version tag 2023-06-30 02:06:12 +02:00
Harald Barth
9c5e48c3d5 test more tolerant alg 2023-06-30 02:05:10 +02:00
pmantoine
1bdb05a471 ESP32 now sets hostname to dccex in STA mode 2023-06-29 11:00:14 +08:00
Harald Barth
c2fa76c76a version 2.4.61 2023-06-26 20:01:56 +02:00
Harald Barth
35fd912c60 MAX_CURRENT restriction (caps motor shield value) 2023-06-26 20:00:03 +02:00
Harald Barth
dfba6c6fc1 version tag 2023-06-23 13:55:34 +02:00
Harald Barth
f3cb263aaa convert mac addr hex chars to lower case to be compatible with AT software 2023-06-23 13:54:25 +02:00
pmantoine
ec6e730559 ESP32 mDNS registration for throttle autodiscovery 2023-06-23 18:08:05 +08:00
Fred
196a27a27a Update WifiESP32.cpp (#338)
* Update WifiESP32.cpp

Fix SSID for AP from DCC_ to DCCEX_

* Update version.h to 4.2.59
2023-06-22 19:47:20 -04:00
Harald Barth
99b6ca025a move ADCee begin as well 2023-06-22 23:30:28 +02:00
Harald Barth
db555e8820 Start motordriver as soon as possible but without waveform 2023-06-22 22:57:59 +02:00
Harald Barth
0d679ad993 version 2023-06-21 10:49:49 +02:00
Harald Barth
dd80260781 add common fault pin handling to new overload code 2023-06-21 10:44:57 +02:00
Harald Barth
08f41415dc format option to write microseconds 2023-06-21 10:43:41 +02:00
Harald Barth
2f65d4347e Merge branch 'devel-overcurrent' into devel 2023-06-20 21:17:28 +02:00
Harald Barth
08c00d275d Merge branch 'devel' into devel-overcurrent 2023-06-19 08:58:32 +02:00
Harald Barth
1888073dc2 set lastPowerChange we doing the power on retry after overload 2023-06-19 08:43:50 +02:00
Harald Barth
6dd175f63b fix power change timer micros overflow 2023-06-19 00:33:53 +02:00
Harald Barth
f83be05220 STM32: Use mask as loop variable 2023-06-18 19:26:38 +02:00
Harald Barth
cade89ba16 check ADCee::init() return value 2023-06-18 09:48:15 +02:00
Harald Barth
277825c530 versiontag 2023-06-18 09:01:32 +02:00
Harald Barth
95fe7aafe0 overload detection code cleanup 2023-06-18 08:59:37 +02:00
Harald Barth
f99deb4276 overload detection different timestamps and verbose diag 2023-06-14 22:57:28 +02:00
Harald Barth
f5d4dcb97c new overload detection 2023-06-14 00:58:02 +02:00
17 changed files with 344 additions and 98 deletions

View File

@@ -78,6 +78,12 @@ void setup()
// 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));
@@ -100,15 +106,8 @@ void setup()
EthernetInterface::setup();
#endif // ETHERNET_ON
// 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.
// 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);
DCC::begin();
// Start RMFT aka EX-RAIL (ignored if no automnation)
RMFT::begin();

View File

@@ -60,8 +60,7 @@ const byte FN_GROUP_5=0x10;
FSH* DCC::shieldName=NULL;
byte DCC::globalSpeedsteps=128;
void DCC::begin(const FSH * motorShieldName) {
shieldName=(FSH *)motorShieldName;
void DCC::begin() {
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

5
DCC.h
View File

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

View File

@@ -467,7 +467,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool prog=false;
bool join=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
if (params==0) { // All
main=true;
prog=true;
}
@@ -487,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;
@@ -500,7 +500,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
bool main=false;
bool prog=false;
if (params > 1) break;
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
if (params==0) { // All
main=true;
prog=true;
}
@@ -516,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,8 +194,10 @@ 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;
}
@@ -212,6 +214,8 @@ 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

@@ -247,6 +247,9 @@ 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-202306182208Z"
#define GITHUB_SHA "devel-overcurrent-202307061457Z"

View File

@@ -27,7 +27,7 @@
#include "DCCTimer.h"
#include "DIAG.h"
bool MotorDriver::commonFaultPin=false;
unsigned long MotorDriver::globalOverloadStart = 0;
volatile portreg_t shadowPORTA;
volatile portreg_t shadowPORTB;
@@ -135,7 +135,11 @@ 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;
rawCurrentTripValue=mA2raw(trip_milliamps);
#ifdef MAX_CURRENT
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
tripMilliamps = MAX_CURRENT;
#endif
rawCurrentTripValue=mA2raw(tripMilliamps);
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never
@@ -159,11 +163,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
}
// prepare values for current detection
sampleDelay = 0;
lastSampleTaken = millis();
progTripValue = mA2raw(TRIP_CURRENT_PROG);
}
bool MotorDriver::isPWMCapable() {
@@ -172,7 +172,12 @@ bool MotorDriver::isPWMCapable() {
void MotorDriver::setPower(POWERMODE mode) {
bool on=mode==POWERMODE::ON;
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);
if (on) {
// when switching a track On, we need to check the crrentOffset with the pin OFF
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
@@ -212,8 +217,8 @@ bool MotorDriver::canMeasureCurrent() {
return currentPin!=UNUSED_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.
* 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.
* 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
@@ -371,64 +376,166 @@ 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:
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;
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 {
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;
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
}
setPower(POWERMODE::ALERT);
break;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
}
// 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 newPowerMode = (powerMode != lastPowerMode);
unsigned long now = micros();
if (newPowerMode)
lastBadSample = now;
lastPowerMode = POWERMODE::ALERT;
// check how long we have been in this state
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
if(checkFault()) {
lastBadSample = now;
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
if ( mslpc < timeout) {
if (newPowerMode)
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);
setPower(POWERMODE::OVERLOAD);
break;
}
if (checkCurrent(useProgLimit)) {
lastBadSample = now;
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
if (newPowerMode) {
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);
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);
}
setPower(POWERMODE::ON);
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.
}
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;
}
}

View File

@@ -107,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 };
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
class MotorDriver {
public:
@@ -175,7 +175,10 @@ class MotorDriver {
bool isPWMCapable();
bool canMeasureCurrent();
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
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;
}
inline byte getFaultPin() {
return faultPin;
}
@@ -186,6 +189,16 @@ 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();
@@ -194,9 +207,22 @@ class MotorDriver {
char trackLetter = '?';
bool isProgTrack = false; // tells us if this is a prog track
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
inline 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;
@@ -217,10 +243,14 @@ class MotorDriver {
int rawCurrentTripValue;
// current sampling
POWERMODE powerMode;
unsigned long lastSampleTaken;
unsigned int sampleDelay;
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
int progTripValue;
int lastCurrent;
int lastCurrent; //temp value
int tripValue; //temp value
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
@@ -228,10 +258,21 @@ class MotorDriver {
int maxmA;
int tripmA;
// 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;
// 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;
// Trip current for programming track, 250mA. Change only if you really
// need to be non-NMRA-compliant because of decoders that are not either.

View File

@@ -117,6 +117,24 @@ 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

@@ -123,10 +123,18 @@ void TrackManager::Setup(const FSH * shieldname,
setTrackMode(1,TRACK_MODE_MAIN);
#endif
// TODO Fault pin config for odd motor boards (example pololu)
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
// && (mainDriver->getFaultPin() != UNUSED_PIN));
DCC::begin(shieldname);
// 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);
}
void TrackManager::addTrack(byte t, MotorDriver* driver) {

View File

@@ -1,4 +1,5 @@
/*
© 2023, Paul M. Antoine
© 2021, Harald Barth.
This file is part of CommandStation-EX
@@ -20,6 +21,7 @@
#if defined(ARDUINO_ARCH_ESP32)
#include <vector>
#include "defines.h"
#include "ESPmDNS.h"
#include <WiFi.h>
#include "esp_wifi.h"
#include "WifiESP32.h"
@@ -105,6 +107,12 @@ 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,
@@ -138,6 +146,7 @@ bool WifiESP::setup(const char *SSid,
havePassword = false;
if (haveSSID && havePassword) {
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
WiFi.mode(WIFI_STA);
#ifdef SERIAL_BT_COMMANDS
WiFi.setSleep(true);
@@ -176,12 +185,14 @@ bool WifiESP::setup(const char *SSid,
}
if (!haveSSID) {
// prepare all strings
String strSSID("DCC_");
String strSSID("DCCEX_");
String strPass("PASS_");
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);
@@ -209,6 +220,15 @@ 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

@@ -57,6 +57,21 @@ 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.

View File

@@ -205,7 +205,7 @@
#define WIFI_SERIAL_LINK_SPEED 115200
#if __has_include ( "myAutomation.h")
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
#define EXRAIL_ACTIVE
#else
#define EXRAIL_WARNING

View File

@@ -1,7 +1,7 @@
#!/bin/bash
#
# © 2022 Harald Barth
# © 2022,2023 Harald Barth
#
# This file is part of CommandStation-EX
#
@@ -29,14 +29,33 @@ 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

@@ -173,6 +173,8 @@ 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

View File

@@ -4,7 +4,15 @@
#include "StringFormatter.h"
#define VERSION "4.2.56"
#define VERSION "4.2.62pre3"
// 4.2.62 - completely new overcurrent detection
// - ESP32 protect from race in RMT code
// 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()