From f5d4dcb97c73cc8693bf557233a278c5925873e4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 14 Jun 2023 00:58:02 +0200 Subject: [PATCH 01/49] new overload detection --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 112 ++++++++++++++++++++++++++++-------------------- MotorDriver.h | 19 +++++--- 3 files changed, 81 insertions(+), 52 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7ef8904..4ac112c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306031954Z" +#define GITHUB_SHA "devel-202306132257Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 678a28a..3bfaaea 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -154,11 +154,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() { @@ -167,6 +163,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 @@ -367,63 +364,86 @@ 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(); + //if (millis() - lastSampleTaken < sampleDelay) return; + //lastSampleTaken = millis(); int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); + + // check if there was a change from last time + if (powerMode != oldPowerMode) { + lastPowerChange = micros(); + oldPowerMode = powerMode; + } - // Trackname for diag messages later switch (powerMode) { case POWERMODE::OFF: - sampleDelay = POWER_SAMPLE_OFF_WAIT; + if (microsSinceLastPowerChange() > 5000000UL) { + power_sample_overload_wait = 100UL; + } 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 - } + // We have a fault pin condition to take care of + lastCurrent = -lastCurrent; + if (commonFaultPin) { + if (lastCurrent < tripValue) { + // probably other track, do a fast toggle. + setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again + 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 { + unsigned long us; + if (lastCurrent < tripValue) { + if (power_sample_overload_wait <= 1000UL && // almost virgin + (us = microsSinceLastPowerChange()) < 50000UL) { // Ignore 50ms fault pin if no current + DIAG(F("TRACK %c FAULT PIN ACTIVE - 50ms ignore %lus"), trackno + 'A', us); + break; + } + lastCurrent = tripValue; // exaggerate + } else { + if (power_sample_overload_wait <= 100UL && // virgin + microsSinceLastPowerChange() < 5000UL) { // Ignore 5ms fault pin if we see current + DIAG(F("TRACK %c FAULT PIN ACTIVE - 5ms ignore"), trackno + 'A'); + break; + } + } + DIAG(F("TRACK %c FAULT PIN ACTIVE - for real"), 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; + if (microsSinceLastPowerChange() > 5000000UL) { + power_sample_overload_wait = 100UL; + } } 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 (power_sample_overload_wait > 100UL || // not virgin + microsSinceLastPowerChange() > 10000UL) { // Longer than 10ms + setPower(POWERMODE::OVERLOAD); + unsigned int mA=raw2mA(lastCurrent); + unsigned int maxmA=raw2mA(tripValue); + DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %lus"), + trackno + 'A', mA, maxmA, 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 + if (microsSinceLastPowerChange() > power_sample_overload_wait) { + // adjust next wait time + power_sample_overload_wait *= 2; + if (power_sample_overload_wait > 10000000UL) + power_sample_overload_wait = 10000000UL; + // 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. + DIAG(F("TRACK %c POWER RESTORE (check %lus)"), trackno + 'A', power_sample_overload_wait); + } + break; + default: + break; } } diff --git a/MotorDriver.h b/MotorDriver.h index be77d2d..a6e0aa7 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -186,6 +186,15 @@ 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 diff = micros() - lastPowerChange; + if (diff > (1UL << (6 *sizeof(unsigned long)))) + lastPowerChange += 1UL << (4 * sizeof(unsigned long)); + return diff; + }; #ifdef ANALOG_READ_INTERRUPT bool sampleCurrentFromHW(); void startCurrentFromHW(); @@ -217,8 +226,8 @@ class MotorDriver { int rawCurrentTripValue; // current sampling POWERMODE powerMode; - unsigned long lastSampleTaken; - unsigned int sampleDelay; + POWERMODE oldPowerMode; + unsigned long lastPowerChange; // timestamp in microseconds int progTripValue; int lastCurrent; #ifdef ANALOG_READ_INTERRUPT @@ -229,9 +238,9 @@ class MotorDriver { 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; + static const int POWER_SAMPLE_ON_WAIT = 1; + static const int POWER_SAMPLE_OFF_WAIT = 100; + static const int POWER_SAMPLE_OVERLOAD_WAIT = 500UL; // Trip current for programming track, 250mA. Change only if you really // need to be non-NMRA-compliant because of decoders that are not either. From f99deb4276e5059382e3f6d96accaeb7cde2ace6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 14 Jun 2023 22:57:28 +0200 Subject: [PATCH 02/49] overload detection different timestamps and verbose diag --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 46 +++++++++++++++++++++++++++++++++++----------- MotorDriver.h | 2 +- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 4ac112c..22bb009 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306132257Z" +#define GITHUB_SHA "devel-202306142056Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 3bfaaea..c1f6ca4 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -364,18 +364,15 @@ 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(); - // check if there was a change from last time - if (powerMode != oldPowerMode) { - lastPowerChange = micros(); - oldPowerMode = powerMode; - } - switch (powerMode) { case POWERMODE::OFF: + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD POFF OFF")); + } if (microsSinceLastPowerChange() > 5000000UL) { power_sample_overload_wait = 100UL; } @@ -385,6 +382,11 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { lastCurrent=getCurrentRaw(); if (lastCurrent < 0) { // We have a fault pin condition to take care of + if (!overloadNow) { + overloadNow=true; + lastPowerChange = micros(); + DIAG(F("OVERLOAD FPIN ON")); + } lastCurrent = -lastCurrent; if (commonFaultPin) { if (lastCurrent < tripValue) { @@ -416,22 +418,44 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { } // // // if (lastCurrent < tripValue) { + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD PON OFF")); + } if (microsSinceLastPowerChange() > 5000000UL) { power_sample_overload_wait = 100UL; } } else { // too much current + if (!overloadNow) { + overloadNow=true; + lastPowerChange = micros(); + DIAG(F("OVERLOAD PON ON")); + } + unsigned long us = microsSinceLastPowerChange(); if (power_sample_overload_wait > 100UL || // not virgin - microsSinceLastPowerChange() > 10000UL) { // Longer than 10ms + us > 10000UL) { // Longer than 10ms setPower(POWERMODE::OVERLOAD); + // the setPower just turned off, so overload is now gone + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD PON OFF")); + } unsigned int mA=raw2mA(lastCurrent); unsigned int maxmA=raw2mA(tripValue); - DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %lus"), - trackno + 'A', mA, maxmA, power_sample_overload_wait); + DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown after %lus for %lus"), + trackno + 'A', mA, maxmA, us, power_sample_overload_wait); } } break; case POWERMODE::OVERLOAD: + if (overloadNow) { + overloadNow=false; + lastPowerChange = micros(); + DIAG(F("OVERLOAD POVER OFF")); + } // Try setting it back on after the OVERLOAD_WAIT if (microsSinceLastPowerChange() > power_sample_overload_wait) { // adjust next wait time diff --git a/MotorDriver.h b/MotorDriver.h index a6e0aa7..89a2e08 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -226,7 +226,7 @@ class MotorDriver { int rawCurrentTripValue; // current sampling POWERMODE powerMode; - POWERMODE oldPowerMode; + bool overloadNow = false; unsigned long lastPowerChange; // timestamp in microseconds int progTripValue; int lastCurrent; From 95fe7aafe09aede12c091d1a5c036d255f37de11 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 08:59:37 +0200 Subject: [PATCH 03/49] overload detection code cleanup --- MotorDriver.cpp | 65 ++++++++++++++++++++++++++++--------------------- MotorDriver.h | 17 ++++++++++--- 2 files changed, 50 insertions(+), 32 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index c1f6ca4..b1a89eb 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -369,12 +369,13 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { switch (powerMode) { case POWERMODE::OFF: if (overloadNow) { + // reset overload condition as we have just turned off power + // DIAG(F("OVERLOAD POFF OFF")); overloadNow=false; lastPowerChange = micros(); - DIAG(F("OVERLOAD POFF OFF")); } - if (microsSinceLastPowerChange() > 5000000UL) { - power_sample_overload_wait = 100UL; + if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { + power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; } break; case POWERMODE::ON: @@ -383,9 +384,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { 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; lastPowerChange = micros(); - DIAG(F("OVERLOAD FPIN ON")); } lastCurrent = -lastCurrent; if (commonFaultPin) { @@ -398,73 +400,80 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A'); } } else { - unsigned long us; if (lastCurrent < tripValue) { - if (power_sample_overload_wait <= 1000UL && // almost virgin - (us = microsSinceLastPowerChange()) < 50000UL) { // Ignore 50ms fault pin if no current - DIAG(F("TRACK %c FAULT PIN ACTIVE - 50ms ignore %lus"), trackno + 'A', us); + 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 + lastCurrent = tripValue; // exaggerate so condition below (*) is true } else { - if (power_sample_overload_wait <= 100UL && // virgin - microsSinceLastPowerChange() < 5000UL) { // Ignore 5ms fault pin if we see current - DIAG(F("TRACK %c FAULT PIN ACTIVE - 5ms ignore"), trackno + 'A'); + 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 ACTIVE - for real"), trackno + 'A'); + DIAG(F("TRACK %c FAULT PIN ACTIVE"), trackno + 'A'); } } // // // - if (lastCurrent < tripValue) { + // 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; lastPowerChange = micros(); - DIAG(F("OVERLOAD PON OFF")); } - if (microsSinceLastPowerChange() > 5000000UL) { - power_sample_overload_wait = 100UL; + 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; lastPowerChange = micros(); - DIAG(F("OVERLOAD PON ON")); } - unsigned long us = microsSinceLastPowerChange(); - if (power_sample_overload_wait > 100UL || // not virgin - us > 10000UL) { // Longer than 10ms + 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); - // the setPower just turned off, so overload is now gone if (overloadNow) { + // the setPower just turned off, so overload is now gone + // DIAG(F("OVERLOAD PON OFF")); overloadNow=false; lastPowerChange = micros(); - DIAG(F("OVERLOAD PON OFF")); } unsigned int mA=raw2mA(lastCurrent); unsigned int maxmA=raw2mA(tripValue); DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown after %lus for %lus"), - trackno + 'A', mA, maxmA, us, power_sample_overload_wait); + trackno + 'A', mA, maxmA, uSecs, power_sample_overload_wait); } } break; case POWERMODE::OVERLOAD: if (overloadNow) { + // state overload mode means power is off, turn off overload condition flag as well + // DIAG(F("OVERLOAD POVER OFF")); overloadNow=false; lastPowerChange = micros(); - DIAG(F("OVERLOAD POVER OFF")); } // Try setting it back on after the OVERLOAD_WAIT if (microsSinceLastPowerChange() > power_sample_overload_wait) { // adjust next wait time power_sample_overload_wait *= 2; - if (power_sample_overload_wait > 10000000UL) - power_sample_overload_wait = 10000000UL; + if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX) + power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX; // power on test setPower(POWERMODE::ON); - DIAG(F("TRACK %c POWER RESTORE (check %lus)"), trackno + 'A', power_sample_overload_wait); + DIAG(F("TRACK %c POWER RESTORE (was off %lus)"), trackno + 'A', power_sample_overload_wait); } break; default: diff --git a/MotorDriver.h b/MotorDriver.h index 89a2e08..fe577bd 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -237,10 +237,19 @@ class MotorDriver { int maxmA; int tripmA; - // Wait times for power management. Unit: milliseconds - static const int POWER_SAMPLE_ON_WAIT = 1; - static const int POWER_SAMPLE_OFF_WAIT = 100; - static const int POWER_SAMPLE_OVERLOAD_WAIT = 500UL; + // 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. From 277825c5305fcd2c40c24839705537230559d302 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 09:01:32 +0200 Subject: [PATCH 04/49] versiontag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 22bb009..37d7d5f 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306142056Z" +#define GITHUB_SHA "devel-202306180700Z" From cade89ba1692f12b610368c4f67897a2d4fe7fa8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 09:48:15 +0200 Subject: [PATCH 05/49] check ADCee::init() return value --- MotorDriver.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index b1a89eb..5ed8684 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -108,8 +108,13 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } currentPin=current_pin; - if (currentPin!=UNUSED_PIN) - ADCee::init(currentPin); + if (currentPin!=UNUSED_PIN) { + int ret = ADCee::init(currentPin); + if (ret < -1010) { // XXX give value a name later + DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin); + currentPin = UNUSED_PIN; + } + } senseOffset=0; // value can not be obtained until waveform is activated if (fault_pin != UNUSED_PIN) { From f83be0522034d3ad9c9cdefd473095eb431b6a89 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 19:26:38 +0200 Subject: [PATCH 06/49] STM32: Use mask as loop variable --- DCCTimerSTM32.cpp | 28 +++++++++++++++++++--------- GITHUB_SHA.h | 2 +- TrackManager.h | 12 +++++++++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index a45c4d4..7ed2b95 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -30,6 +30,10 @@ #ifdef ARDUINO_ARCH_STM32 #include "DCCTimer.h" +#ifdef DEBUG_ADC +#include "TrackManager.h" +#endif +#include "DIAG.h" #if defined(ARDUINO_NUCLEO_F411RE) // Nucleo-64 boards don't have Serial1 defined by default @@ -307,6 +311,8 @@ int ADCee::init(uint8_t pin) { analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready + DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id); + return value; } @@ -340,11 +346,13 @@ void ADCee::scan() { // found value analogvals[id] = ADC1->DR; // advance at least one track - // for scope debug TrackManager::track[1]->setBrake(0); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(0); +#endif waiting = false; id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (mask == 0) { // the 1 has been shifted out id = 0; mask = 1; } @@ -355,18 +363,20 @@ void ADCee::scan() { // look for a valid track to sample or until we are around while (true) { if (mask & usedpins) { - // start new ADC aquire on id + // start new ADC aquire on id ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART - // for scope debug TrackManager::track[1]->setBrake(1); - waiting = true; - return; +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(1); +#endif + waiting = true; + return; } id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; + if (mask == 0) { // the 1 has been shifted out + id = 0; + mask = 1; } } } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 37d7d5f..8bd3812 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306180700Z" +#define GITHUB_SHA "devel-202306181725Z" diff --git a/TrackManager.h b/TrackManager.h index ef4a47c..19e756d 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -84,8 +84,15 @@ class TrackManager { static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main - static bool progTrackBoosted; // true when prog track is not current limited - + static bool progTrackBoosted; // true when prog track is not current limited + +#ifdef DEBUG_ADC + public: +#else + private: +#endif + static MotorDriver* track[MAX_TRACKS]; + private: static void addTrack(byte t, MotorDriver* driver); static byte lastTrack; @@ -93,7 +100,6 @@ class TrackManager { static POWERMODE mainPowerGuess; static void applyDCSpeed(byte t); - static MotorDriver* track[MAX_TRACKS]; static TRACK_MODE trackMode[MAX_TRACKS]; static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX #ifdef ARDUINO_ARCH_ESP32 From befb41ce981e8e8fcece4c00f242a143f3e2cf80 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 09:48:15 +0200 Subject: [PATCH 07/49] check ADCee::init() return value --- MotorDriver.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 678a28a..e41e150 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -108,8 +108,13 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i } currentPin=current_pin; - if (currentPin!=UNUSED_PIN) - ADCee::init(currentPin); + if (currentPin!=UNUSED_PIN) { + int ret = ADCee::init(currentPin); + if (ret < -1010) { // XXX give value a name later + DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin); + currentPin = UNUSED_PIN; + } + } senseOffset=0; // value can not be obtained until waveform is activated if (fault_pin != UNUSED_PIN) { From 02669368758977411c83ffb3896556dd37b547e4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 19:26:38 +0200 Subject: [PATCH 08/49] STM32: Use mask as loop variable --- DCCTimerSTM32.cpp | 28 +++++++++++++++++++--------- GITHUB_SHA.h | 2 +- TrackManager.h | 12 +++++++++--- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index a45c4d4..7ed2b95 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -30,6 +30,10 @@ #ifdef ARDUINO_ARCH_STM32 #include "DCCTimer.h" +#ifdef DEBUG_ADC +#include "TrackManager.h" +#endif +#include "DIAG.h" #if defined(ARDUINO_NUCLEO_F411RE) // Nucleo-64 boards don't have Serial1 defined by default @@ -307,6 +311,8 @@ int ADCee::init(uint8_t pin) { analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready + DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id); + return value; } @@ -340,11 +346,13 @@ void ADCee::scan() { // found value analogvals[id] = ADC1->DR; // advance at least one track - // for scope debug TrackManager::track[1]->setBrake(0); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(0); +#endif waiting = false; id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (mask == 0) { // the 1 has been shifted out id = 0; mask = 1; } @@ -355,18 +363,20 @@ void ADCee::scan() { // look for a valid track to sample or until we are around while (true) { if (mask & usedpins) { - // start new ADC aquire on id + // start new ADC aquire on id ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART - // for scope debug TrackManager::track[1]->setBrake(1); - waiting = true; - return; +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(1); +#endif + waiting = true; + return; } id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { - id = 0; - mask = 1; + if (mask == 0) { // the 1 has been shifted out + id = 0; + mask = 1; } } } diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 7ef8904..8bd3812 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306031954Z" +#define GITHUB_SHA "devel-202306181725Z" diff --git a/TrackManager.h b/TrackManager.h index ef4a47c..19e756d 100644 --- a/TrackManager.h +++ b/TrackManager.h @@ -84,8 +84,15 @@ class TrackManager { static int16_t joinRelay; static bool progTrackSyncMain; // true when prog track is a siding switched to main - static bool progTrackBoosted; // true when prog track is not current limited - + static bool progTrackBoosted; // true when prog track is not current limited + +#ifdef DEBUG_ADC + public: +#else + private: +#endif + static MotorDriver* track[MAX_TRACKS]; + private: static void addTrack(byte t, MotorDriver* driver); static byte lastTrack; @@ -93,7 +100,6 @@ class TrackManager { static POWERMODE mainPowerGuess; static void applyDCSpeed(byte t); - static MotorDriver* track[MAX_TRACKS]; static TRACK_MODE trackMode[MAX_TRACKS]; static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX #ifdef ARDUINO_ARCH_ESP32 From 7783837545d1f6f937726083c79605cb23242603 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 18 Jun 2023 21:08:52 +0200 Subject: [PATCH 09/49] Back out this as it is bigger and slower This reverts commit efb26660603eb02d056b1d6c9c7a7b4124213269. --- DCCTimer.h | 14 ++++---- DCCTimerAVR.cpp | 91 ++++++++++++++++++++++++++----------------------- 2 files changed, 55 insertions(+), 50 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 7a9d940..29b8819 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -105,9 +105,14 @@ private: // that an offset can be initialized. class ADCee { public: - // init does add the pin to the list of scanned pins (if this + // begin is called for any setup that must be done before + // **init** can be called. On some architectures this involves ADC + // initialisation and clock routing, sampling times etc. + static void begin(); + // init adds the pin to the list of scanned pins (if this // platform's implementation scans pins) and returns the first - // read value. It is called before the regular scan is started. + // read value (which is why it required begin to have been called first!) + // It must be called before the regular scan is started. static int init(uint8_t pin); // read does read the pin value from the scanned cache or directly // if this is a platform that does not scan. fromISR is a hint if @@ -116,9 +121,6 @@ public: static int read(uint8_t pin, bool fromISR=false); // returns possible max value that the ADC can return static int16_t ADCmax(); - // begin is called for any setup that must be done before - // scan can be called. - static void begin(); private: // On platforms that scan, it is called from waveform ISR // only on a regular basis. @@ -127,8 +129,6 @@ private: static uint16_t usedpins; // cached analog values (malloc:ed to actual number of ADC channels) static int *analogvals; - // ids to scan (new way) - static byte *idarr; // friend so that we can call scan() and begin() friend class DCCWaveform; }; diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 40ce0fb..9b16c47 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -123,14 +123,13 @@ void DCCTimer::reset() { } #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#define NUM_ADC_INPUTS 16 +#define NUM_ADC_INPUTS 15 #else -#define NUM_ADC_INPUTS 8 +#define NUM_ADC_INPUTS 7 #endif uint16_t ADCee::usedpins = 0; int * ADCee::analogvals = NULL; -byte *ADCee::idarr = NULL; -static bool ADCusesHighPort = false; +bool ADCusesHighPort = false; /* * Register a new pin to be scanned @@ -139,28 +138,16 @@ static bool ADCusesHighPort = false; */ int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; - byte n; - if (id >= NUM_ADC_INPUTS) + if (id > NUM_ADC_INPUTS) return -1023; if (id > 7) ADCusesHighPort = true; pinMode(pin, INPUT); int value = analogRead(pin); - if (analogvals == NULL) { - analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int)); - for (n=0 ; n < NUM_ADC_INPUTS; n++) // set unreasonable value at startup as marker - analogvals[n] = -32768; // 16 bit int min value - idarr = (byte *)calloc(NUM_ADC_INPUTS+1, sizeof(byte)); // +1 for terminator value - for (n=0 ; n <= NUM_ADC_INPUTS; n++) - idarr[n] = 255; // set 255 as end of array marker - } - analogvals[id] = value; // store before enable by idarr[n] - for (n=0 ; n <= NUM_ADC_INPUTS; n++) { - if (idarr[n] == 255) { - idarr[n] = id; - break; - } - } + if (analogvals == NULL) + analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogvals[id] = value; + usedpins |= (1<setBrake(0); waiting = false; + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } } if (!waiting) { - // cycle around in-use analogue pins - num++; - if (idarr[num] == 255) - num = 0; - // start new ADC aquire on id + if (usedpins == 0) // otherwise we would loop forever + return; + // look for a valid track to sample or until we are around + while (true) { + if (mask & usedpins) { + // start new ADC aquire on id #if defined(ADCSRB) && defined(MUX5) - if (ADCusesHighPort) { // if we ever have started to use high pins) - if (idarr[num] > 7) // if we use a high ADC pin - bitSet(ADCSRB, MUX5); // set MUX5 bit - else - bitClear(ADCSRB, MUX5); - } + if (ADCusesHighPort) { // if we ever have started to use high pins) + if (id > 7) // if we use a high ADC pin + bitSet(ADCSRB, MUX5); // set MUX5 bit + else + bitClear(ADCSRB, MUX5); + } #endif - ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX - bitSet(ADCSRA, ADSC); // start conversion - waiting = true; + ADMUX=(1<setBrake(1); + waiting = true; + return; + } + id++; + mask = mask << 1; + if (id == NUM_ADC_INPUTS+1) { + id = 0; + mask = 1; + } + } } } #pragma GCC pop_options @@ -231,4 +236,4 @@ void ADCee::begin() { //bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX interrupts(); } -#endif \ No newline at end of file +#endif From 56fcb4e5f790086010c357a7da912ca066fb0532 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 00:06:04 +0200 Subject: [PATCH 10/49] Optimize DCCTimerARV.cpp --- DCCTimer.h | 1 + DCCTimerAVR.cpp | 39 +++++++++++++++++++++++++-------------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/DCCTimer.h b/DCCTimer.h index 29b8819..7402f16 100644 --- a/DCCTimer.h +++ b/DCCTimer.h @@ -127,6 +127,7 @@ private: static void scan(); // bit array of used pins (max 16) static uint16_t usedpins; + static uint8_t highestPin; // cached analog values (malloc:ed to actual number of ADC channels) static int *analogvals; // friend so that we can call scan() and begin() diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 9b16c47..80cd245 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -1,6 +1,6 @@ /* * © 2021 Mike S - * © 2021-2022 Harald Barth + * © 2021-2023 Harald Barth * © 2021 Fred Decker * © 2021 Chris Harlow * © 2021 David Cutting @@ -29,6 +29,9 @@ #include #include #include "DCCTimer.h" +#ifdef DEBUG_ADC +#include "TrackManager.h" +#endif INTERRUPT_CALLBACK interruptHandler=0; // Arduino nano, uno, mega etc @@ -123,13 +126,14 @@ void DCCTimer::reset() { } #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) -#define NUM_ADC_INPUTS 15 +#define NUM_ADC_INPUTS 16 #else -#define NUM_ADC_INPUTS 7 +#define NUM_ADC_INPUTS 8 #endif uint16_t ADCee::usedpins = 0; +uint8_t ADCee::highestPin = 0; int * ADCee::analogvals = NULL; -bool ADCusesHighPort = false; +static bool ADCusesHighPort = false; /* * Register a new pin to be scanned @@ -138,16 +142,17 @@ bool ADCusesHighPort = false; */ int ADCee::init(uint8_t pin) { uint8_t id = pin - A0; - if (id > NUM_ADC_INPUTS) + if (id >= NUM_ADC_INPUTS) return -1023; if (id > 7) ADCusesHighPort = true; pinMode(pin, INPUT); int value = analogRead(pin); if (analogvals == NULL) - analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int)); + analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int)); analogvals[id] = value; usedpins |= (1< highestPin) highestPin = id; return value; } int16_t ADCee::ADCmax() { @@ -157,13 +162,15 @@ int16_t ADCee::ADCmax() { * Read function ADCee::read(pin) to get value instead of analogRead(pin) */ int ADCee::read(uint8_t pin, bool fromISR) { - (void)fromISR; // AVR does ignore this arg uint8_t id = pin - A0; if ((usedpins & (1<setBrake(0); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(0); +#endif waiting = false; id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (id > highestPin) { // the 1 has been shifted out id = 0; mask = 1; } @@ -212,13 +221,15 @@ void ADCee::scan() { #endif ADMUX=(1<setBrake(1); +#ifdef DEBUG_ADC + if (id == 1) TrackManager::track[1]->setBrake(1); +#endif waiting = true; return; } id++; mask = mask << 1; - if (id == NUM_ADC_INPUTS+1) { + if (id > highestPin) { id = 0; mask = 1; } From d5dad767a42eed0730de8f288b7f0d5261151395 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 00:09:27 +0200 Subject: [PATCH 11/49] version 4.2.55 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8bd3812..25e57bf 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306181725Z" +#define GITHUB_SHA "devel-202306182208Z" diff --git a/version.h b/version.h index c3d47d9..6355f6b 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.54" +#define VERSION "4.2.55" +// 4.2.55 - Optimize analog read for AVR // 4.2.54 - EX8874 shield in config.example.h // - Fix: Better warnings for pin number errors // - Fix: Default roster list possible in Withrottle and From 0cf81d589ec2d1ac5aafb6eb1272c6e2447e5dfb Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:25:20 +1000 Subject: [PATCH 12/49] Add _writeAnalogue() --- IO_RotaryEncoder.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 00a8249..2c5a5ec 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -1,4 +1,5 @@ /* + * © 2023, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation @@ -28,9 +29,23 @@ * ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position * IFRE(vpin, position) - test to see if specified rotary encoder position has been received * -* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. * A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. * +* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other +* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished +* using the EXRAIL SERVO(vpin, position, profile) command, where: +* - vpin = the third defined Vpin (any other is ignored) +* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255 +* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance +* +* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples: +* +* #include "IO_RotaryEncoder.h" +* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software +* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software +* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software +* * Refer to the documentation for further information including the valid activities and examples. */ @@ -103,6 +118,15 @@ private: I2CManager.write(_I2CAddress, _feedbackBuffer, 2); } } + + void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { + if (vpin == _firstVpin + 2) { + if (position >= 0 && position <= 255) { + byte _positionBuffer[2] = {RE_MOVE, position}; + I2CManager.write(_I2CAddress, _positionBuffer, 2); + } + } + } void _display() override { DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, @@ -120,6 +144,7 @@ private: enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation + RE_MOVE = 0xA2, // Flag for sending a position update }; }; From 955ff4f96d591084c086ec48e5f3c5a185122abf Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:25:20 +1000 Subject: [PATCH 13/49] Add _writeAnalogue() --- IO_RotaryEncoder.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 00a8249..2c5a5ec 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -1,4 +1,5 @@ /* + * © 2023, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation @@ -28,9 +29,23 @@ * ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position * IFRE(vpin, position) - test to see if specified rotary encoder position has been received * -* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. * A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. * +* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other +* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished +* using the EXRAIL SERVO(vpin, position, profile) command, where: +* - vpin = the third defined Vpin (any other is ignored) +* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255 +* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance +* +* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples: +* +* #include "IO_RotaryEncoder.h" +* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software +* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software +* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software +* * Refer to the documentation for further information including the valid activities and examples. */ @@ -103,6 +118,15 @@ private: I2CManager.write(_I2CAddress, _feedbackBuffer, 2); } } + + void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { + if (vpin == _firstVpin + 2) { + if (position >= 0 && position <= 255) { + byte _positionBuffer[2] = {RE_MOVE, position}; + I2CManager.write(_I2CAddress, _positionBuffer, 2); + } + } + } void _display() override { DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, @@ -120,6 +144,7 @@ private: enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation + RE_MOVE = 0xA2, // Flag for sending a position update }; }; From 6dd175f63bddeb361942df0c2725f7974f1c6e7a Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 00:33:53 +0200 Subject: [PATCH 14/49] fix power change timer micros overflow --- GITHUB_SHA.h | 2 +- MotorDriver.h | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8bd3812..41f732d 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306181725Z" +#define GITHUB_SHA "devel-202306182233Z" diff --git a/MotorDriver.h b/MotorDriver.h index fe577bd..1eb4e95 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -190,9 +190,10 @@ class MotorDriver { // 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 diff = micros() - lastPowerChange; - if (diff > (1UL << (6 *sizeof(unsigned long)))) - lastPowerChange += 1UL << (4 * sizeof(unsigned long)); + 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; }; #ifdef ANALOG_READ_INTERRUPT From 1888073dc230aa18794490c44a94bd1c05620d59 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 19 Jun 2023 08:43:50 +0200 Subject: [PATCH 15/49] set lastPowerChange we doing the power on retry after overload --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 41f732d..8b6faac 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306182233Z" +#define GITHUB_SHA "devel-202306190642Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 5ed8684..0eed19a 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -478,6 +478,10 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { 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. + lastPowerChange = micros(); DIAG(F("TRACK %c POWER RESTORE (was off %lus)"), trackno + 'A', power_sample_overload_wait); } break; From c3eb6b8d8a9774d8e308f8f80d5e51d134d0de18 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 19 Jun 2023 17:47:42 +0800 Subject: [PATCH 16/49] STM32 ADCee highestPin --- DCCTimerAVR.cpp | 6 +++--- DCCTimerSTM32.cpp | 16 ++++++++++------ platformio.ini | 2 +- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/DCCTimerAVR.cpp b/DCCTimerAVR.cpp index 80cd245..3e6c436 100644 --- a/DCCTimerAVR.cpp +++ b/DCCTimerAVR.cpp @@ -199,7 +199,7 @@ void ADCee::scan() { waiting = false; id++; mask = mask << 1; - if (id > highestPin) { // the 1 has been shifted out + if (id > highestPin) { id = 0; mask = 1; } @@ -230,8 +230,8 @@ void ADCee::scan() { id++; mask = mask << 1; if (id > highestPin) { - id = 0; - mask = 1; + id = 0; + mask = 1; } } } diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 7ed2b95..17ad84e 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -233,13 +233,16 @@ void DCCTimer::reset() { while(true) {}; } -#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS - // TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs! #if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) #warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing! #endif +// For now, define the max of 16 ports - some variants have more, but this not **yet** supported +#define NUM_ADC_INPUTS 16 +// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS + uint16_t ADCee::usedpins = 0; +uint8_t ADCee::highestPin = 0; int * ADCee::analogvals = NULL; uint32_t * analogchans = NULL; bool adc1configured = false; @@ -310,6 +313,7 @@ int ADCee::init(uint8_t pin) { analogvals[id] = value; // Store sampled value analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin usedpins |= (1 << id); // This pin is now ready + if (id > highestPin) highestPin = id; // Store our highest pin in use DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id); @@ -352,7 +356,7 @@ void ADCee::scan() { waiting = false; id++; mask = mask << 1; - if (mask == 0) { // the 1 has been shifted out + if (id > highestPin) { // the 1 has been shifted out id = 0; mask = 1; } @@ -374,9 +378,9 @@ void ADCee::scan() { } id++; mask = mask << 1; - if (mask == 0) { // the 1 has been shifted out - id = 0; - mask = 1; + if (id > highestPin) { + id = 0; + mask = 1; } } } diff --git a/platformio.ini b/platformio.ini index d85c76c..3091d7f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -188,7 +188,7 @@ platform = ststm32 board = nucleo_f446re framework = arduino lib_deps = ${env.lib_deps} -build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO +build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO monitor_speed = 115200 monitor_echo = yes From 0be9af22707ba84a770d5a57ed865956a1d1bdec Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 20 Jun 2023 09:37:19 +1000 Subject: [PATCH 17/49] Update some logic --- IO_RotaryEncoder.h | 106 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 2c5a5ec..3391f6d 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -59,50 +59,98 @@ class RotaryEncoder : public IODevice { public: - // Constructor - RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){ - _firstVpin = firstVpin; - _nPins = nPins; - _I2CAddress = i2cAddress; - addDevice(this); - } + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); } private: + // Constructor + RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){ + _firstVpin = firstVpin; + _nPins = nPins; + if (_nPins > 3) { + _nPins = 3; + DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins); + } + _I2CAddress = i2cAddress; + addDevice(this); + } + // Initiate the device void _begin() { + uint8_t _status; + // Attempt to initilalise device I2CManager.begin(); if (I2CManager.exists(_I2CAddress)) { - byte _getVersion[1] = {RE_VER}; - I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); - _majorVer = _versionBuffer[0]; - _minorVer = _versionBuffer[1]; - _patchVer = _versionBuffer[2]; - _buffer[0] = RE_OP; - I2CManager.write(_I2CAddress, _buffer, 1); + // Send RE_OP, must receive RE_OP to be online + _sendBuffer[0] = RE_OP; + _status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1); + if (_status == I2C_STATUS_OK) { + if (_rcvBuffer[0] == RE_OP) { + _sendBuffer[0] = RE_VER; + if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) { + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; + } + } else { + DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]); + _deviceState = DEVSTATE_FAILED; + return; + } + } else { + DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + return; + } + // byte _getVersion[1] = {RE_VER}; + // I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); + // _majorVer = _versionBuffer[0]; + // _minorVer = _versionBuffer[1]; + // _patchVer = _versionBuffer[2]; + // _buffer[0] = RE_OP; + // I2CManager.write(_I2CAddress, _buffer, 1); #ifdef DIAG_IO _display(); #endif } else { - _deviceState = DEVSTATE_FAILED; + DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; } } void _loop(unsigned long currentMicros) override { - I2CManager.read(_I2CAddress, _buffer, 1); - _position = _buffer[0]; - // This here needs to have a change check, ie. position is a different value. - #if defined(EXRAIL_ACTIVE) + if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed + if (_i2crb.isBusy()) return; // Return if I2C operation still in progress + + if (currentMicros - _lastPositionRead > _positionRefresh) { + _lastPositionRead = currentMicros; + _sendBuffer[0] = RE_READ; + I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder + _position = _rcvBuffer[0]; + // If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use +#if defined(EXRAIL_ACTIVE) if (_position != _previousPosition) { _previousPosition = _position; - RMFT2::changeEvent(_firstVpin,1); + RMFT2::changeEvent(_firstVpin, 1); } else { - RMFT2::changeEvent(_firstVpin,0); + RMFT2::changeEvent(_firstVpin, 0); } - #endif - delayUntil(currentMicros + 100000); +#endif + } + // I2CManager.read(_I2CAddress, _buffer, 1); + // _position = _buffer[0]; + // // This here needs to have a change check, ie. position is a different value. + // #if defined(EXRAIL_ACTIVE) + // if (_position != _previousPosition) { + // _previousPosition = _position; + // RMFT2::changeEvent(_firstVpin,1); + // } else { + // RMFT2::changeEvent(_firstVpin,0); + // } + // #endif + // delayUntil(currentMicros + 100000); } // Device specific read function @@ -122,7 +170,8 @@ private: void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { if (vpin == _firstVpin + 2) { if (position >= 0 && position <= 255) { - byte _positionBuffer[2] = {RE_MOVE, position}; + byte newPosition = position & 0xFF; + byte _positionBuffer[2] = {RE_MOVE, newPosition}; I2CManager.write(_I2CAddress, _positionBuffer, 2); } } @@ -136,15 +185,20 @@ private: int8_t _position; int8_t _previousPosition = 0; uint8_t _versionBuffer[3]; - uint8_t _buffer[1]; + uint8_t _sendBuffer[1]; + uint8_t _rcvBuffer[1]; uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; + I2CRB _i2crb; + unsigned long _lastPositionRead = 0; + const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation - RE_MOVE = 0xA2, // Flag for sending a position update + RE_MOVE = 0xA2, // Flag for sending a position update from the device driver to the encoder + RE_READ = 0xA3, // Flag to read the current position of the encoder }; }; From 988011475c84e7f02a7adb2a17a637cc8075a4b0 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Tue, 20 Jun 2023 08:34:27 +0800 Subject: [PATCH 18/49] STM32 Serial port handling for WiFi --- DCCTimerSTM32.cpp | 16 +++++++++------- WifiInterface.cpp | 41 +++++++++++++++++++++++++++-------------- defines.h | 2 -- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 17ad84e..cc60547 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -36,23 +36,25 @@ #include "DIAG.h" #if defined(ARDUINO_NUCLEO_F411RE) -// Nucleo-64 boards don't have Serial1 defined by default +// Nucleo-64 boards don't have additional serial ports defined by default HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. // Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s) -HardwareSerial Serial3(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE +HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE #elif defined(ARDUINO_NUCLEO_F446RE) -// Nucleo-64 boards don't have Serial1 defined by default +// Nucleo-64 boards don't have additional serial ports defined by default +// On the F446RE, Serial1 isn't really useable as it's Rx/Tx pair sit on already used D2/D10 pins // HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE // Serial2 is defined to use USART2 by default, but is in fact used as the diag console // via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. -HardwareSerial Serial1(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE -HardwareSerial Serial3(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE -// NB: USART3 and USART6 are available but as yet undefined +// On the F446RE, Serial3 and Serial5 are easy to use: +HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE +HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE +// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins #elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) // Nucleo-144 boards don't have Serial1 defined by default -HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 +HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 // Serial3 is defined to use USART3 by default, but is in fact used as the diag console // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. #else diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 732b393..3a88aea 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -52,20 +52,32 @@ Stream * WifiInterface::wifiStream; #if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)) #define NUM_SERIAL 3 +#define SERIAL1 Serial1 +#define SERIAL3 Serial3 +#endif + +#if defined(ARDUINO_ARCH_STM32) +// Handle serial ports availability on STM32 for variants! +// #undef NUM_SERIAL +#if defined(ARDUINO_NUCLEO_F411RE) +#define NUM_SERIAL 3 +#define SERIAL1 Serial1 +#define SERIAL3 Serial6 +#elif defined(ARDUINO_NUCLEO_F446RE) +#define NUM_SERIAL 3 +#define SERIAL1 Serial3 +#define SERIAL3 Serial5 +#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) +#define NUM_SERIAL 2 +#define SERIAL1 Serial6 +#endif #endif #ifndef NUM_SERIAL #define NUM_SERIAL 1 +#define SERIAL1 Serial1 #endif -// For STM32 we need to define Serial3 in the platform specific -// DCCTimerSTM32.cpp file, we here make the assumption that it -// exists to link against. -#ifdef ARDUINO_ARCH_STM32 -#if NUM_SERIAL > 2 -extern HardwareSerial Serial3; -#endif -#endif bool WifiInterface::setup(long serial_link_speed, const FSH *wifiESSID, const FSH *wifiPassword, @@ -84,14 +96,15 @@ bool WifiInterface::setup(long serial_link_speed, (void) port; (void) channel; #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); + SERIAL1.begin(serial_link_speed); + wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel); #endif // Other serials are tried, depending on hardware. -// Currently only the Arduino Mega 2560 has usable Serial2 +// Currently only the Arduino Mega 2560 has usable Serial2 (Nucleo-64 boards use Serial 2 for console!) #if defined(ARDUINO_AVR_MEGA2560) #if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS) if (wifiUp == WIFI_NOAT) @@ -107,8 +120,8 @@ bool WifiInterface::setup(long serial_link_speed, #if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS) if (wifiUp == WIFI_NOAT) { - Serial3.begin(serial_link_speed); - wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel); + SERIAL3.begin(serial_link_speed); + wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel); } #endif diff --git a/defines.h b/defines.h index 3f5c3ba..48f7c6b 100644 --- a/defines.h +++ b/defines.h @@ -147,8 +147,6 @@ #ifndef I2C_USE_WIRE #define I2C_USE_WIRE #endif - #undef NUM_SERIAL - #define NUM_SERIAL 3 /* TODO when ready #elif defined(ARDUINO_ARCH_RP2040) From c9323251204030db247b02744c393f4a566a4d9a Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Mon, 19 Jun 2023 08:25:20 +1000 Subject: [PATCH 19/49] Add _writeAnalogue() --- IO_RotaryEncoder.h | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 00a8249..2c5a5ec 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -1,4 +1,5 @@ /* + * © 2023, Peter Cole. All rights reserved. * © 2022, Peter Cole. All rights reserved. * * This file is part of EX-CommandStation @@ -28,9 +29,23 @@ * ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position * IFRE(vpin, position) - test to see if specified rotary encoder position has been received * -* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. +* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin. * A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished. * +* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other +* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished +* using the EXRAIL SERVO(vpin, position, profile) command, where: +* - vpin = the third defined Vpin (any other is ignored) +* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255 +* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance +* +* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples: +* +* #include "IO_RotaryEncoder.h" +* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software +* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software +* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software +* * Refer to the documentation for further information including the valid activities and examples. */ @@ -103,6 +118,15 @@ private: I2CManager.write(_I2CAddress, _feedbackBuffer, 2); } } + + void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { + if (vpin == _firstVpin + 2) { + if (position >= 0 && position <= 255) { + byte _positionBuffer[2] = {RE_MOVE, position}; + I2CManager.write(_I2CAddress, _positionBuffer, 2); + } + } + } void _display() override { DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer, @@ -120,6 +144,7 @@ private: enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation + RE_MOVE = 0xA2, // Flag for sending a position update }; }; From 247cea6dc12f517ac57a7802e5d3887088132c66 Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 20 Jun 2023 09:37:19 +1000 Subject: [PATCH 20/49] Update some logic --- IO_RotaryEncoder.h | 106 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 2c5a5ec..3391f6d 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -59,50 +59,98 @@ class RotaryEncoder : public IODevice { public: - // Constructor - RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){ - _firstVpin = firstVpin; - _nPins = nPins; - _I2CAddress = i2cAddress; - addDevice(this); - } + static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) { if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress); } private: + // Constructor + RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){ + _firstVpin = firstVpin; + _nPins = nPins; + if (_nPins > 3) { + _nPins = 3; + DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins); + } + _I2CAddress = i2cAddress; + addDevice(this); + } + // Initiate the device void _begin() { + uint8_t _status; + // Attempt to initilalise device I2CManager.begin(); if (I2CManager.exists(_I2CAddress)) { - byte _getVersion[1] = {RE_VER}; - I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); - _majorVer = _versionBuffer[0]; - _minorVer = _versionBuffer[1]; - _patchVer = _versionBuffer[2]; - _buffer[0] = RE_OP; - I2CManager.write(_I2CAddress, _buffer, 1); + // Send RE_OP, must receive RE_OP to be online + _sendBuffer[0] = RE_OP; + _status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1); + if (_status == I2C_STATUS_OK) { + if (_rcvBuffer[0] == RE_OP) { + _sendBuffer[0] = RE_VER; + if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) { + _majorVer = _versionBuffer[0]; + _minorVer = _versionBuffer[1]; + _patchVer = _versionBuffer[2]; + } + } else { + DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]); + _deviceState = DEVSTATE_FAILED; + return; + } + } else { + DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; + return; + } + // byte _getVersion[1] = {RE_VER}; + // I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); + // _majorVer = _versionBuffer[0]; + // _minorVer = _versionBuffer[1]; + // _patchVer = _versionBuffer[2]; + // _buffer[0] = RE_OP; + // I2CManager.write(_I2CAddress, _buffer, 1); #ifdef DIAG_IO _display(); #endif } else { - _deviceState = DEVSTATE_FAILED; + DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString()); + _deviceState = DEVSTATE_FAILED; } } void _loop(unsigned long currentMicros) override { - I2CManager.read(_I2CAddress, _buffer, 1); - _position = _buffer[0]; - // This here needs to have a change check, ie. position is a different value. - #if defined(EXRAIL_ACTIVE) + if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed + if (_i2crb.isBusy()) return; // Return if I2C operation still in progress + + if (currentMicros - _lastPositionRead > _positionRefresh) { + _lastPositionRead = currentMicros; + _sendBuffer[0] = RE_READ; + I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder + _position = _rcvBuffer[0]; + // If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use +#if defined(EXRAIL_ACTIVE) if (_position != _previousPosition) { _previousPosition = _position; - RMFT2::changeEvent(_firstVpin,1); + RMFT2::changeEvent(_firstVpin, 1); } else { - RMFT2::changeEvent(_firstVpin,0); + RMFT2::changeEvent(_firstVpin, 0); } - #endif - delayUntil(currentMicros + 100000); +#endif + } + // I2CManager.read(_I2CAddress, _buffer, 1); + // _position = _buffer[0]; + // // This here needs to have a change check, ie. position is a different value. + // #if defined(EXRAIL_ACTIVE) + // if (_position != _previousPosition) { + // _previousPosition = _position; + // RMFT2::changeEvent(_firstVpin,1); + // } else { + // RMFT2::changeEvent(_firstVpin,0); + // } + // #endif + // delayUntil(currentMicros + 100000); } // Device specific read function @@ -122,7 +170,8 @@ private: void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override { if (vpin == _firstVpin + 2) { if (position >= 0 && position <= 255) { - byte _positionBuffer[2] = {RE_MOVE, position}; + byte newPosition = position & 0xFF; + byte _positionBuffer[2] = {RE_MOVE, newPosition}; I2CManager.write(_I2CAddress, _positionBuffer, 2); } } @@ -136,15 +185,20 @@ private: int8_t _position; int8_t _previousPosition = 0; uint8_t _versionBuffer[3]; - uint8_t _buffer[1]; + uint8_t _sendBuffer[1]; + uint8_t _rcvBuffer[1]; uint8_t _majorVer = 0; uint8_t _minorVer = 0; uint8_t _patchVer = 0; + I2CRB _i2crb; + unsigned long _lastPositionRead = 0; + const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms enum { RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device RE_OP = 0xA1, // Flag for normal operation - RE_MOVE = 0xA2, // Flag for sending a position update + RE_MOVE = 0xA2, // Flag for sending a position update from the device driver to the encoder + RE_READ = 0xA3, // Flag to read the current position of the encoder }; }; From 2af01e3c426fbce18212bc2870cee46c5381bbfa Mon Sep 17 00:00:00 2001 From: peteGSX <97784652+peteGSX@users.noreply.github.com> Date: Tue, 20 Jun 2023 12:48:13 +1000 Subject: [PATCH 21/49] Add ready flag --- IO_RotaryEncoder.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 3391f6d..e3ac5c5 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -83,11 +83,11 @@ private: // Attempt to initilalise device I2CManager.begin(); if (I2CManager.exists(_I2CAddress)) { - // Send RE_OP, must receive RE_OP to be online - _sendBuffer[0] = RE_OP; + // Send RE_RDY, must receive RE_RDY to be online + _sendBuffer[0] = RE_RDY; _status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1); if (_status == I2C_STATUS_OK) { - if (_rcvBuffer[0] == RE_OP) { + if (_rcvBuffer[0] == RE_RDY) { _sendBuffer[0] = RE_VER; if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) { _majorVer = _versionBuffer[0]; @@ -195,10 +195,11 @@ private: const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms enum { - RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device - RE_OP = 0xA1, // Flag for normal operation - RE_MOVE = 0xA2, // Flag for sending a position update from the device driver to the encoder - RE_READ = 0xA3, // Flag to read the current position of the encoder + RE_RDY = 0xA0, // Flag to check if encoder is ready for operation + RE_VER = 0xA1, // Flag to retrieve rotary encoder software version + RE_READ = 0xA2, // Flag to read the current position of the encoder + RE_OP = 0xA3, // Flag for operation start/end, sent to when sending feedback on move start/end + RE_MOVE = 0xA4, // Flag for sending a position update from the device driver to the encoder }; }; From 2b8b99530760951bbfa3ff86968aaaa0c0bce65c Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 20 Jun 2023 19:24:49 +1000 Subject: [PATCH 22/49] Updated logic, sending move --- IO_RotaryEncoder.h | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index e3ac5c5..42d8a2d 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -104,13 +104,6 @@ private: _deviceState = DEVSTATE_FAILED; return; } - // byte _getVersion[1] = {RE_VER}; - // I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1); - // _majorVer = _versionBuffer[0]; - // _minorVer = _versionBuffer[1]; - // _patchVer = _versionBuffer[2]; - // _buffer[0] = RE_OP; - // I2CManager.write(_I2CAddress, _buffer, 1); #ifdef DIAG_IO _display(); #endif @@ -139,18 +132,6 @@ private: } #endif } - // I2CManager.read(_I2CAddress, _buffer, 1); - // _position = _buffer[0]; - // // This here needs to have a change check, ie. position is a different value. - // #if defined(EXRAIL_ACTIVE) - // if (_position != _previousPosition) { - // _previousPosition = _position; - // RMFT2::changeEvent(_firstVpin,1); - // } else { - // RMFT2::changeEvent(_firstVpin,0); - // } - // #endif - // delayUntil(currentMicros + 100000); } // Device specific read function From 995c6f8ede39284d1a78ebf2b6e965a486c0672a Mon Sep 17 00:00:00 2001 From: peteGSX Date: Tue, 20 Jun 2023 19:32:43 +1000 Subject: [PATCH 23/49] Update version --- version.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/version.h b/version.h index 6355f6b..ab5609d 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.55" +#define VERSION "4.2.56" +// 4.2.56 - Update IO_RotaryEncoder.h: +// - Improved I2C communication, non-blocking reads +// - Enable sending positions to the encoder from EXRAIL via SERVO() // 4.2.55 - Optimize analog read for AVR // 4.2.54 - EX8874 shield in config.example.h // - Fix: Better warnings for pin number errors From 08f41415dc37fcd85c39857ce69776f17d1e5439 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 21 Jun 2023 10:43:41 +0200 Subject: [PATCH 24/49] format option to write microseconds --- StringFormatter.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/StringFormatter.cpp b/StringFormatter.cpp index f7d9c50..c475ef0 100644 --- a/StringFormatter.cpp +++ b/StringFormatter.cpp @@ -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 '-': From dd80260781b51dedf6e45faa537eb6bdb72be5f4 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 21 Jun 2023 10:44:57 +0200 Subject: [PATCH 25/49] add common fault pin handling to new overload code --- DCCEXParser.cpp | 4 ++-- MotorDriver.cpp | 49 +++++++++++++++++++----------------------------- MotorDriver.h | 20 ++++++++++++++++++-- TrackManager.cpp | 14 +++++++++++--- 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 048ae2a..1e3df90 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -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; } diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 0eed19a..bf5c47b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -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; @@ -377,7 +377,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // reset overload condition as we have just turned off power // DIAG(F("OVERLOAD POFF OFF")); overloadNow=false; - lastPowerChange = micros(); + setLastPowerChange(); } if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; @@ -392,24 +392,15 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // turn on overload condition as fault pin has gone active // DIAG(F("OVERLOAD FPIN ON")); overloadNow=true; - lastPowerChange = micros(); + setLastPowerChangeOverload(); } lastCurrent = -lastCurrent; - if (commonFaultPin) { - if (lastCurrent < tripValue) { - // probably other track, do a fast toggle. - setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again - 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 { + { 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'); + DIAG(F("TRACK %c FAULT PIN (50ms ignore)"), trackno + 'A'); break; } lastCurrent = tripValue; // exaggerate so condition below (*) is true @@ -417,11 +408,11 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { 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'); + DIAG(F("TRACK %c FAULT PIN (5ms ignore)"), trackno + 'A'); break; } } - DIAG(F("TRACK %c FAULT PIN ACTIVE"), trackno + 'A'); + DIAG(F("TRACK %c FAULT PIN"), trackno + 'A'); } } // // // @@ -432,7 +423,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // current is below trip value, turn off overload condition // DIAG(F("OVERLOAD PON OFF")); overloadNow=false; - lastPowerChange = micros(); + setLastPowerChange(); } if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) { power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT; @@ -443,7 +434,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // current is over trip value, turn on overload condition // DIAG(F("OVERLOAD PON ON")); overloadNow=true; - lastPowerChange = micros(); + setLastPowerChange(); } unsigned long uSecs = microsSinceLastPowerChange(); if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin @@ -454,24 +445,20 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // the setPower just turned off, so overload is now gone // DIAG(F("OVERLOAD PON OFF")); overloadNow=false; - lastPowerChange = micros(); + setLastPowerChangeOverload(); } unsigned int mA=raw2mA(lastCurrent); unsigned int maxmA=raw2mA(tripValue); - DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown after %lus for %lus"), + 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: - if (overloadNow) { - // state overload mode means power is off, turn off overload condition flag as well - // DIAG(F("OVERLOAD POVER OFF")); - overloadNow=false; - lastPowerChange = micros(); - } + { // Try setting it back on after the OVERLOAD_WAIT - if (microsSinceLastPowerChange() > power_sample_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) @@ -481,10 +468,12 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { // 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. - lastPowerChange = micros(); - DIAG(F("TRACK %c POWER RESTORE (was off %lus)"), trackno + 'A', power_sample_overload_wait); + overloadNow=false; + setLastPowerChange(); + DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc); } - break; + } + break; default: break; } diff --git a/MotorDriver.h b/MotorDriver.h index 1eb4e95..b268aea 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -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; } @@ -196,6 +199,17 @@ class MotorDriver { 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(); @@ -228,7 +242,9 @@ class MotorDriver { // current sampling POWERMODE powerMode; bool overloadNow = false; - unsigned long lastPowerChange; // timestamp in microseconds + 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 diff --git a/TrackManager.cpp b/TrackManager.cpp index 9c0f0b0..dd0fb4a 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -123,9 +123,17 @@ 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)); + // 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::begin(shieldname); } From 0d679ad993662d9c5b6885bdb203a7759404686b Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Wed, 21 Jun 2023 10:49:49 +0200 Subject: [PATCH 26/49] version --- GITHUB_SHA.h | 2 +- version.h | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 8b6faac..57a2187 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306190642Z" +#define GITHUB_SHA "devel-202306210849Z" diff --git a/version.h b/version.h index ab5609d..fc470c8 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.56" +#define VERSION "4.2.57" +// 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() From db555e882059199b94fc23be16e379ce23065fe6 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 22 Jun 2023 22:57:59 +0200 Subject: [PATCH 27/49] Start motordriver as soon as possible but without waveform --- CommandStation-EX.ino | 8 +++----- DCC.cpp | 3 +-- DCC.h | 5 ++++- GITHUB_SHA.h | 2 +- TrackManager.cpp | 2 +- version.h | 3 ++- 6 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 7363566..cbe55a3 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -77,6 +77,8 @@ void setup() // Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::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 @@ -104,11 +106,7 @@ void setup() // 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(); diff --git a/DCC.cpp b/DCC.cpp index c9535b2..30fcf5f 100644 --- a/DCC.cpp +++ b/DCC.cpp @@ -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("\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA)); #ifndef DISABLE_EEPROM // Load stuff from EEprom diff --git a/DCC.h b/DCC.h index 15d5a4f..74b4e77 100644 --- a/DCC.h +++ b/DCC.h @@ -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 diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 57a2187..549ccda 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306210849Z" +#define GITHUB_SHA "devel-202306222055Z" diff --git a/TrackManager.cpp b/TrackManager.cpp index dd0fb4a..8f66677 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -134,7 +134,7 @@ void TrackManager::Setup(const FSH * shieldname, } } } - DCC::begin(shieldname); + DCC::setShieldName(shieldname); } void TrackManager::addTrack(byte t, MotorDriver* driver) { diff --git a/version.h b/version.h index fc470c8..75228d0 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.57" +#define VERSION "4.2.58" +// 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: From 99b6ca025a9f841586131c35ea10c91853d860af Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 22 Jun 2023 23:30:28 +0200 Subject: [PATCH 28/49] move ADCee begin as well --- CommandStation-EX.ino | 9 +++++---- GITHUB_SHA.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index cbe55a3..24a0aef 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -77,7 +77,11 @@ void setup() // Initialise HAL layer before reading EEprom or setting up MotorDrivers IODevice::begin(); -// Set up MotorDrivers early to initialize all pins + + // 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 ( @@ -102,9 +106,6 @@ 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. DCC::begin(); diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 549ccda..c495610 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306222055Z" +#define GITHUB_SHA "devel-202306222129Z" From 196a27a27abf1ddab5c685a7ed8489dd68306890 Mon Sep 17 00:00:00 2001 From: Fred Date: Thu, 22 Jun 2023 19:47:20 -0400 Subject: [PATCH 29/49] Update WifiESP32.cpp (#338) * Update WifiESP32.cpp Fix SSID for AP from DCC_ to DCCEX_ * Update version.h to 4.2.59 --- WifiESP32.cpp | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 7754e11..ef02d8d 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -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); diff --git a/version.h b/version.h index 75228d0..cfad079 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.58" +#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 From ec6e730559f3af3268dc22b6d44a31e62a03edd9 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Fri, 23 Jun 2023 18:08:05 +0800 Subject: [PATCH 30/49] ESP32 mDNS registration for throttle autodiscovery --- WifiESP32.cpp | 10 ++++++++++ platformio.ini | 2 ++ version.h | 3 ++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index ef02d8d..9a74138 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -20,6 +20,7 @@ #if defined(ARDUINO_ARCH_ESP32) #include #include "defines.h" +#include "ESPmDNS.h" #include #include "esp_wifi.h" #include "WifiESP32.h" @@ -209,6 +210,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 diff --git a/platformio.ini b/platformio.ini index 3091d7f..1a87770 100644 --- a/platformio.ini +++ b/platformio.ini @@ -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 diff --git a/version.h b/version.h index cfad079..9fd3001 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.59" +#define VERSION "4.2.60" +// 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) From f3cb263aaac48fee2052af9b3fa0c47f2d594187 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 23 Jun 2023 13:54:25 +0200 Subject: [PATCH 31/49] convert mac addr hex chars to lower case to be compatible with AT software --- WifiESP32.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 9a74138..bc22d16 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -106,6 +106,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, @@ -183,6 +189,8 @@ bool WifiESP::setup(const char *SSid, 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); From dfba6c6fc13ef8e3b0eb7c3521b3ff0aa8274a5f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 23 Jun 2023 13:55:34 +0200 Subject: [PATCH 32/49] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c495610..6a1e625 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306222129Z" +#define GITHUB_SHA "devel-202306231154Z" From 35fd912c6094c3fbe6ec76fd40e2288c4d6c2405 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 26 Jun 2023 20:00:03 +0200 Subject: [PATCH 33/49] MAX_CURRENT restriction (caps motor shield value) --- MotorDriver.cpp | 6 +++++- config.example.h | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index bf5c47b..12a263c 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -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 diff --git a/config.example.h b/config.example.h index 16402b6..a945ec9 100644 --- a/config.example.h +++ b/config.example.h @@ -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. From c2fa76c76aba19039d9bf77a1a0754d06c4cd1f2 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 26 Jun 2023 20:01:56 +0200 Subject: [PATCH 34/49] version 2.4.61 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6a1e625..6ae3e6c 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306231154Z" +#define GITHUB_SHA "devel-202306261801Z" diff --git a/version.h b/version.h index 9fd3001..2325ceb 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.60" +#define VERSION "4.2.61" +// 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 From 1bdb05a471e7f140b22fd51ae72834fe2e9d0ebc Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 29 Jun 2023 11:00:14 +0800 Subject: [PATCH 35/49] ESP32 now sets hostname to dccex in STA mode --- WifiESP32.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/WifiESP32.cpp b/WifiESP32.cpp index bc22d16..6e09aa1 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -1,4 +1,5 @@ /* + © 2023, Paul M. Antoine © 2021, Harald Barth. This file is part of CommandStation-EX @@ -145,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); From 9c5e48c3d5f9b4c6a99bb713468a8dc6bec54e88 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 30 Jun 2023 02:05:10 +0200 Subject: [PATCH 36/49] test more tolerant alg --- MotorDriver.cpp | 17 ++++++++++++----- MotorDriver.h | 4 ++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 12a263c..1d7b2bc 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -401,22 +401,26 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { lastCurrent = -lastCurrent; { if (lastCurrent < tripValue) { - if (power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) && // almost virgin + 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 + setPower(POWERMODE::OVERLOAD); + overloadNow=false; + DIAG(F("TRACK %c FAULT PIN"), trackno + 'A'); + break; + //lastCurrent = tripValue; // exaggerate so condition below (*) is true } else { - if (power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && // virgin + 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'); + DIAG(F("TRACK %c FAULT PIN AND OVERCURRENT"), trackno + 'A'); } } // // // @@ -441,8 +445,11 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { setLastPowerChange(); } unsigned long uSecs = microsSinceLastPowerChange(); - if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin + if (/*power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || */ // not virgin uSecs > POWER_SAMPLE_OFF_DELAY) { + /* + if (micros() - overloadStart > POWER_SAMPLE_OFF_DELAY) { + */ // Overload has existed longer than delay (typ. 10ms) setPower(POWERMODE::OVERLOAD); if (overloadNow) { diff --git a/MotorDriver.h b/MotorDriver.h index b268aea..b6f4670 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -256,7 +256,7 @@ 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 = 100UL; + static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 10000UL; // 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 @@ -264,7 +264,7 @@ class MotorDriver { // 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; + static const unsigned long POWER_SAMPLE_OFF_DELAY = 100000UL; // Upper limit for retry period static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL; From 7c41ec7c25efc8d8576ffa8943b72f2135bd7986 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Fri, 30 Jun 2023 02:06:12 +0200 Subject: [PATCH 37/49] version tag --- GITHUB_SHA.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6ae3e6c..0f05328 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202306261801Z" +#define GITHUB_SHA "devel-overcurrent-202306300005Z" From 5d17f247de761a972af83b642668bd728e465644 Mon Sep 17 00:00:00 2001 From: peteGSX Date: Sat, 1 Jul 2023 05:18:45 +1000 Subject: [PATCH 38/49] RotaryEnoder, EX-Turntable fixes --- IO_EXTurntable.h | 2 +- IO_RotaryEncoder.h | 8 ++++++-- version.h | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/IO_EXTurntable.h b/IO_EXTurntable.h index 40c02ee..29ce679 100644 --- a/IO_EXTurntable.h +++ b/IO_EXTurntable.h @@ -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; } } diff --git a/IO_RotaryEncoder.h b/IO_RotaryEncoder.h index 42d8a2d..9d40b34 100644 --- a/IO_RotaryEncoder.h +++ b/IO_RotaryEncoder.h @@ -134,12 +134,13 @@ private: } } - // Device specific read function + // Return the position sent by the rotary encoder software int _readAnalogue(VPIN vpin) override { if (_deviceState == DEVSTATE_FAILED) return 0; return _position; } + // Send the feedback value to the rotary encoder software void _write(VPIN vpin, int value) override { if (vpin == _firstVpin + 1) { if (value != 0) value = 0x01; @@ -148,9 +149,12 @@ 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) { + if (position >= 0 && position <= 255 && position != _position) { byte newPosition = position & 0xFF; byte _positionBuffer[2] = {RE_MOVE, newPosition}; I2CManager.write(_I2CAddress, _positionBuffer, 2); diff --git a/version.h b/version.h index 2325ceb..ee7eb49 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,10 @@ #include "StringFormatter.h" -#define VERSION "4.2.61" +#define VERSION "4.2.62" +// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position +// - Update IO_EXTurntable.h to remove forced I2C clock speed +// - Show device offline if EX-Turntable not connected // 4.2.61 - MAX_CURRENT restriction (caps motor shield value) // 4.2.60 - Add mDNS capability to ESP32 for autodiscovery // 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_ From 70d4c016ef79e09af5189735154073061ff85693 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Jul 2023 01:33:41 +0200 Subject: [PATCH 39/49] completely new overcurrent detection --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 193 +++++++++++++++++++++++------------------------- MotorDriver.h | 48 ++++++------ version.h | 3 +- 4 files changed, 121 insertions(+), 125 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 0f05328..07d9123 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202306300005Z" +#define GITHUB_SHA "devel-overcurrent-202307012332Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 1d7b2bc..e05059b 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -173,7 +173,11 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { if (powerMode == mode) return; - bool on=mode==POWERMODE::ON; + //DIAG(F("POWERMODE=%d"), (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) { @@ -213,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 @@ -373,118 +377,103 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res } void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { - int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue(); switch (powerMode) { - case POWERMODE::OFF: - 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 - 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; - } - setPower(POWERMODE::OVERLOAD); - overloadNow=false; - DIAG(F("TRACK %c FAULT PIN"), 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 AND OVERCURRENT"), 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; - } + + 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 { - // 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) { - /* - if (micros() - overloadStart > 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); - } + DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A'); } + setPower(POWERMODE::ALERT); break; - case POWERMODE::OVERLOAD: - { - // Try setting it back on after the OVERLOAD_WAIT - unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange()); + } + // 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); + } + 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; + power_sample_overload_wait *= 4; 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; } diff --git a/MotorDriver.h b/MotorDriver.h index b6f4670..0ebf494 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -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: @@ -192,24 +192,13 @@ 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() { + inline unsigned long microsSinceLastPowerChange(POWERMODE mode) { unsigned long now = micros(); - unsigned long diff = now - lastPowerChange; + unsigned long diff = now - lastPowerChange[(int)mode]; if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds - lastPowerChange = now - 30000000UL; // 30 seconds ago + lastPowerChange[(int)mode] = 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(); @@ -218,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; @@ -241,12 +243,14 @@ class MotorDriver { int rawCurrentTripValue; // current sampling POWERMODE powerMode; - bool overloadNow = false; - unsigned long lastPowerChange; // timestamp in microseconds + 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; @@ -259,12 +263,14 @@ class MotorDriver { static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 10000UL; // 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 = 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 = 100000UL; + static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL; // Upper limit for retry period static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL; diff --git a/version.h b/version.h index 2325ceb..3741dc4 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.61" +#define VERSION "4.2.62pre1" +// 4.2.62 - completely new overcurrent detection // 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_ From ab1356d07061fd056da9dbe718206126297a2cbf Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Jul 2023 13:55:56 +0200 Subject: [PATCH 40/49] Change first join/unjoin and set power after that --- DCCEXParser.cpp | 4 ++-- GITHUB_SHA.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index 1e3df90..aa635ae 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -487,9 +487,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) #endif else break; // will reply } + TrackManager::setJoin(join); if (main) TrackManager::setMainPower(POWERMODE::ON); if (prog) TrackManager::setProgPower(POWERMODE::ON); - TrackManager::setJoin(join); CommandDistributor::broadcastPower(); return; @@ -516,12 +516,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) else break; // will reply } + 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; diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 07d9123..c676511 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307012332Z" +#define GITHUB_SHA "devel-overcurrent-202307021155Z" From 10c59028e1525f1c12949d2c4a5434183cb241c8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sun, 2 Jul 2023 20:33:29 +0200 Subject: [PATCH 41/49] Add documentation --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 63 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c676511..ddcb181 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307021155Z" +#define GITHUB_SHA "devel-overcurrent-202307021833Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index e05059b..64bb8ca 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -173,7 +173,7 @@ bool MotorDriver::isPWMCapable() { void MotorDriver::setPower(POWERMODE mode) { if (powerMode == mode) return; - //DIAG(F("POWERMODE=%d"), (int)mode); + //DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode); lastPowerChange[(int)mode] = micros(); if (mode == POWERMODE::OVERLOAD) globalOverloadStart = lastPowerChange[(int)mode]; @@ -376,6 +376,67 @@ 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 (50ms) +// 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 (10ms) 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) { switch (powerMode) { From 96a46f36c2a4008b11377b82352d7f39b02911ec Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 3 Jul 2023 00:21:52 +0200 Subject: [PATCH 42/49] Adjust overcurrent timeouts --- GITHUB_SHA.h | 2 +- MotorDriver.cpp | 6 +++--- MotorDriver.h | 4 ++-- version.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index ddcb181..f2440cb 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307021833Z" +#define GITHUB_SHA "devel-overcurrent-202307022222Z" diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 64bb8ca..dc82044 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -400,7 +400,7 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res // 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 (50ms) +// 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 @@ -413,7 +413,7 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res // 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 (10ms) at first entry +// 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. @@ -525,7 +525,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD)); if (mslpc > power_sample_overload_wait) { // adjust next wait time - power_sample_overload_wait *= 4; + 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 diff --git a/MotorDriver.h b/MotorDriver.h index 0ebf494..454015d 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -260,13 +260,13 @@ 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 = 10000UL; + 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 = 50000UL; + 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 diff --git a/version.h b/version.h index 3741dc4..e445bb1 100644 --- a/version.h +++ b/version.h @@ -4,7 +4,7 @@ #include "StringFormatter.h" -#define VERSION "4.2.62pre1" +#define VERSION "4.2.62pre2" // 4.2.62 - completely new overcurrent detection // 4.2.61 - MAX_CURRENT restriction (caps motor shield value) // 4.2.60 - Add mDNS capability to ESP32 for autodiscovery From ae2bbbf668f4233600d6415caa0a8db6782a8dfa Mon Sep 17 00:00:00 2001 From: Nathan Kellenicki Date: Wed, 28 Jun 2023 22:29:53 -0700 Subject: [PATCH 43/49] Added WIFI_FORCE_AP to force AP mode when specifying SSID/pass --- CommandStation-EX.ino | 4 ++-- WifiESP32.cpp | 29 ++++++++++++++++------------- WifiESP32.h | 3 ++- WifiInterface.cpp | 36 +++++++++++++++++++++--------------- WifiInterface.h | 7 ++++--- config.example.h | 5 +++++ 6 files changed, 50 insertions(+), 34 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index 24a0aef..a6885ff 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -95,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); + WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); #endif // WIFI_ON #else // ESP32 needs wifi on always - WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL); + WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP); #endif // ARDUINO_ARCH_ESP32 #if ETHERNET_ON diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 6e09aa1..3fe91ae 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -117,7 +117,8 @@ bool WifiESP::setup(const char *SSid, const char *password, const char *hostname, int port, - const byte channel) { + const byte channel, + const bool forceAP) { bool havePassword = true; bool haveSSID = true; bool wifiUp = false; @@ -145,7 +146,7 @@ bool WifiESP::setup(const char *SSid, if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0) havePassword = false; - if (haveSSID && havePassword) { + if (haveSSID && havePassword && !forceAP) { WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE! WiFi.mode(WIFI_STA); #ifdef SERIAL_BT_COMMANDS @@ -183,18 +184,20 @@ bool WifiESP::setup(const char *SSid, } } } - if (!haveSSID) { + if (!haveSSID || forceAP) { // prepare all strings - 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); + 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); + } WiFi.mode(WIFI_AP); #ifdef SERIAL_BT_COMMANDS diff --git a/WifiESP32.h b/WifiESP32.h index 100e393..1cf0d91 100644 --- a/WifiESP32.h +++ b/WifiESP32.h @@ -31,7 +31,8 @@ public: const char *wifiPassword, const char *hostname, const int port, - const byte channel); + const byte channel, + const bool forceAP); static void loop(); private: }; diff --git a/WifiInterface.cpp b/WifiInterface.cpp index 3a88aea..c377319 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -83,7 +83,8 @@ bool WifiInterface::setup(long serial_link_speed, const FSH *wifiPassword, const FSH *hostname, const int port, - const byte channel) { + const byte channel, + const bool forceAP) { wifiSerialState wifiUp = WIFI_NOAT; @@ -100,7 +101,7 @@ bool WifiInterface::setup(long serial_link_speed, // 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); + wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP); #endif // Other serials are tried, depending on hardware. @@ -110,7 +111,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); + wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP); } #endif #endif @@ -121,7 +122,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); + wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP); } #endif @@ -139,7 +140,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) { + const FSH* hostname, int port, byte channel, bool forceAP) { wifiSerialState wifiState; static uint8_t ntry = 0; ntry++; @@ -148,7 +149,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con DIAG(F("++ Wifi Setup Try %d ++"), ntry); - wifiState = setup2( SSid, password, hostname, port, channel); + wifiState = setup2( SSid, password, hostname, port, channel, forceAP); if (wifiState == WIFI_NOAT) { LCD(4, F("WiFi no AT chip")); @@ -172,7 +173,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) { + const FSH* hostname, int port, byte channel, bool forceAP) { bool ipOK = false; bool oldCmd = false; @@ -225,7 +226,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, if (!checkForOK(1000, F("0.0.0.0"), true,false)) ipOK = true; } - } else { + } else if (!forceAP) { // SSID was configured, so we assume station (client) mode. if (oldCmd) { // AT command early version supports CWJAP/CWSAP @@ -285,14 +286,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, i=0; do { - 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); + 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); + } } else { - // password configured by user - StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR", - macTail, password, channel); + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%s\",\"%s\",%d,4\r\n"), + oldCmd ? "" : "_CUR", SSid, 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) diff --git a/WifiInterface.h b/WifiInterface.h index 7c0a433..33bc211 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -36,17 +36,18 @@ public: const FSH *wifiPassword, const FSH *hostname, const int port, - const byte channel); + const byte channel, + const bool forceAP); 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); + const FSH *hostname, int port, byte channel, bool forceAP); static Stream *wifiStream; static DCCEXParser parser; static wifiSerialState setup2(const FSH *SSSid, const FSH *password, - const FSH *hostname, int port, byte channel); + const FSH *hostname, int port, byte channel, bool forceAP); 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; diff --git a/config.example.h b/config.example.h index a945ec9..9fe6135 100644 --- a/config.example.h +++ b/config.example.h @@ -123,6 +123,11 @@ 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 ///////////////////////////////////////////////////////////////////////////////////// // From b3251e89d7371c486c5d7dc1281c520e85d4dca9 Mon Sep 17 00:00:00 2001 From: Nathan Kellenicki Date: Sun, 2 Jul 2023 19:50:38 -0700 Subject: [PATCH 44/49] Fixed Arduino --- WifiInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WifiInterface.cpp b/WifiInterface.cpp index c377319..bd9cf22 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -297,7 +297,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password, macTail, password, channel); } } else { - StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%s\",\"%s\",%d,4\r\n"), + StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR", SSid, password, channel); } } while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok From e6a40e622c7ab89ee94335595ad4de245a9e6ecc Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 3 Jul 2023 23:43:21 +0200 Subject: [PATCH 45/49] download graphic installer if DISPLAY --- installer.sh | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/installer.sh b/installer.sh index 17a9c04..4821284 100755 --- a/installer.sh +++ b/installer.sh @@ -1,7 +1,7 @@ #!/bin/bash # -# © 2022 Harald Barth +# © 2022,2023 Harald Barth # # This file is part of CommandStation-EX # @@ -29,14 +29,28 @@ 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 [ 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 From f19db3aa5cc956833cb59198841542c796425d8f Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Tue, 4 Jul 2023 16:25:15 +0200 Subject: [PATCH 46/49] DISABLE_PROG does count as less RAM as well --- GITHUB_SHA.h | 2 +- defines.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index f2440cb..6238066 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307022222Z" +#define GITHUB_SHA "devel-overcurrent-202307041424Z" diff --git a/defines.h b/defines.h index 48f7c6b..f40ed2f 100644 --- a/defines.h +++ b/defines.h @@ -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 From c2fcdddd1f079c98d5746b9c561e7d40e2c292ce Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 6 Jul 2023 15:19:44 +0200 Subject: [PATCH 47/49] ESP32 protect from race in RMT code --- DCCRMT.cpp | 4 ++++ DCCWaveform.cpp | 3 +++ GITHUB_SHA.h | 2 +- version.h | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/DCCRMT.cpp b/DCCRMT.cpp index 631cc16..cbd9af6 100644 --- a/DCCRMT.cpp +++ b/DCCRMT.cpp @@ -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--; diff --git a/DCCWaveform.cpp b/DCCWaveform.cpp index e065648..4a99997 100644 --- a/DCCWaveform.cpp +++ b/DCCWaveform.cpp @@ -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); { diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 6238066..2b88c4b 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307041424Z" +#define GITHUB_SHA "devel-overcurrent-202307061318Z" diff --git a/version.h b/version.h index e445bb1..93df891 100644 --- a/version.h +++ b/version.h @@ -4,8 +4,9 @@ #include "StringFormatter.h" -#define VERSION "4.2.62pre2" +#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_ From 4192c1f5a331a0127a20cdd0201b2e720cd906d8 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Thu, 6 Jul 2023 16:58:36 +0200 Subject: [PATCH 48/49] Do not invoke graphical install on Raspbian --- GITHUB_SHA.h | 2 +- installer.sh | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 2b88c4b..5c0ffb6 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307061318Z" +#define GITHUB_SHA "devel-overcurrent-202307061457Z" diff --git a/installer.sh b/installer.sh index 4821284..857710e 100755 --- a/installer.sh +++ b/installer.sh @@ -38,6 +38,11 @@ function need () { 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 From b44bebc1c6bb3320077982b3e7bd3c7d22ecf581 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Sat, 8 Jul 2023 08:58:00 +0200 Subject: [PATCH 49/49] copyright, version and compile warnings fix --- CommandStation-EX.ino | 1 + GITHUB_SHA.h | 2 +- WifiESP32.cpp | 5 +++-- WifiESP32.h | 3 ++- WifiInterface.cpp | 2 ++ WifiInterface.h | 1 + config.example.h | 1 + version.h | 3 ++- 8 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CommandStation-EX.ino b/CommandStation-EX.ino index a6885ff..77e8f40 100644 --- a/CommandStation-EX.ino +++ b/CommandStation-EX.ino @@ -30,6 +30,7 @@ * © 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 diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index 5c0ffb6..5ec114e 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-overcurrent-202307061457Z" +#define GITHUB_SHA "devel-202307080654Z" diff --git a/WifiESP32.cpp b/WifiESP32.cpp index 3fe91ae..28a15fe 100644 --- a/WifiESP32.cpp +++ b/WifiESP32.cpp @@ -1,6 +1,7 @@ /* - © 2023, Paul M. Antoine - © 2021, Harald Barth. + © 2023 Paul M. Antoine + © 2021 Harald Barth + © 2023 Nathan Kellenicki This file is part of CommandStation-EX diff --git a/WifiESP32.h b/WifiESP32.h index 1cf0d91..ec2f560 100644 --- a/WifiESP32.h +++ b/WifiESP32.h @@ -1,5 +1,6 @@ /* - * © 2021, Harald Barth. + * © 2021 Harald Barth + * © 2023 Nathan Kellenicki * * This file is part of CommandStation-EX * diff --git a/WifiInterface.cpp b/WifiInterface.cpp index bd9cf22..20dc235 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -2,6 +2,7 @@ * © 2021 Fred Decker * © 2020-2022 Harald Barth * © 2020-2022 Chris Harlow + * © 2023 Nathan Kellenicki * All rights reserved. * * This file is part of CommandStation-EX @@ -96,6 +97,7 @@ 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 diff --git a/WifiInterface.h b/WifiInterface.h index 33bc211..652156d 100644 --- a/WifiInterface.h +++ b/WifiInterface.h @@ -1,6 +1,7 @@ /* * © 2020-2021 Chris Harlow * © 2020, Harald Barth. + * © 2023 Nathan Kellenicki * All rights reserved. * * This file is part of CommandStation-EX diff --git a/config.example.h b/config.example.h index 9fe6135..0f136f9 100644 --- a/config.example.h +++ b/config.example.h @@ -4,6 +4,7 @@ * © 2020-2023 Harald Barth * © 2020-2021 Fred Decker * © 2020-2021 Chris Harlow + * © 2023 Nathan Kellenicki * * This file is part of CommandStation-EX * diff --git a/version.h b/version.h index 0962b76..7f6bdc7 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "4.2.63" +#define VERSION "4.2.64" +// 4.2.64 - 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