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

Compare commits

...

18 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
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
13 changed files with 207 additions and 76 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;
}
@@ -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;
}

View File

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

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;
@@ -159,11 +159,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,6 +168,7 @@ bool MotorDriver::isPWMCapable() {
void MotorDriver::setPower(POWERMODE mode) {
if (powerMode == mode) return;
bool on=mode==POWERMODE::ON;
if (on) {
// when switching a track On, we need to check the crrentOffset with the pin OFF
@@ -372,63 +369,112 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
}
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;
if (overloadNow) {
// reset overload condition as we have just turned off power
// DIAG(F("OVERLOAD POFF OFF"));
overloadNow=false;
setLastPowerChange();
}
if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_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');
// 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 {
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
if (lastCurrent < tripValue) {
lastCurrent = tripValue; // exaggerate
}
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');
}
}
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;
// // //
// 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 {
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;
// 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;
case POWERMODE::OVERLOAD:
// Try setting it back on after the OVERLOAD_WAIT
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);
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.
// 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);
}
}
break;
default:
break;
}
}

View File

@@ -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,27 @@ 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() {
unsigned long now = micros();
unsigned long diff = now - lastPowerChange;
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
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();
@@ -217,8 +241,10 @@ class MotorDriver {
int rawCurrentTripValue;
// current sampling
POWERMODE powerMode;
unsigned long lastSampleTaken;
unsigned int sampleDelay;
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;
#ifdef ANALOG_READ_INTERRUPT
@@ -228,10 +254,19 @@ 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 = 100UL;
// Time after we consider all faults old and forgotten
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
// How long to ignore fault pin if current is under limit
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_OFF_DELAY = 10000UL;
// 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

@@ -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

@@ -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

@@ -176,7 +176,7 @@ 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);

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,11 @@
#include "StringFormatter.h"
#define VERSION "4.2.56"
#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)
// - 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()