mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2024-11-22 15:46:14 +01:00
673 lines
23 KiB
C++
673 lines
23 KiB
C++
/*
|
|
* © 2022-2024 Paul M Antoine
|
|
* © 2024 Herb Morton
|
|
* © 2021 Mike S
|
|
* © 2021 Fred Decker
|
|
* © 2020-2023 Harald Barth
|
|
* © 2020-2021 Chris Harlow
|
|
* © 2023 Colin Murdoch
|
|
* All rights reserved.
|
|
*
|
|
* This file is part of CommandStation-EX
|
|
*
|
|
* This is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* It is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <Arduino.h>
|
|
#include "MotorDriver.h"
|
|
#include "DCCWaveform.h"
|
|
#include "DCCTimer.h"
|
|
#include "DIAG.h"
|
|
#include "EXRAIL2.h"
|
|
|
|
unsigned long MotorDriver::globalOverloadStart = 0;
|
|
|
|
volatile portreg_t shadowPORTA;
|
|
volatile portreg_t shadowPORTB;
|
|
volatile portreg_t shadowPORTC;
|
|
#if defined(ARDUINO_ARCH_STM32)
|
|
volatile portreg_t shadowPORTD;
|
|
volatile portreg_t shadowPORTE;
|
|
volatile portreg_t shadowPORTF;
|
|
volatile portreg_t shadowPORTG;
|
|
volatile portreg_t shadowPORTH;
|
|
#endif
|
|
|
|
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
|
|
byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
|
|
const FSH * warnString = F("** WARNING **");
|
|
|
|
invertPower=power_pin < 0;
|
|
if (invertPower) {
|
|
powerPin = 0-power_pin;
|
|
IODevice::write(powerPin,HIGH);// set to OUTPUT and off
|
|
} else {
|
|
powerPin = power_pin;
|
|
IODevice::write(powerPin,LOW);// set to OUTPUT and off
|
|
}
|
|
|
|
signalPin=signal_pin;
|
|
getFastPin(F("SIG"),signalPin,fastSignalPin);
|
|
pinMode(signalPin, OUTPUT);
|
|
|
|
fastSignalPin.shadowinout = NULL;
|
|
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
|
|
DIAG(F("Found PORTA pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTA;
|
|
}
|
|
if (HAVE_PORTB(fastSignalPin.inout == &PORTB)) {
|
|
DIAG(F("Found PORTB pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTB;
|
|
}
|
|
if (HAVE_PORTC(fastSignalPin.inout == &PORTC)) {
|
|
DIAG(F("Found PORTC pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTC;
|
|
}
|
|
if (HAVE_PORTD(fastSignalPin.inout == &PORTD)) {
|
|
DIAG(F("Found PORTD pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTD;
|
|
}
|
|
if (HAVE_PORTE(fastSignalPin.inout == &PORTE)) {
|
|
DIAG(F("Found PORTE pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTE;
|
|
}
|
|
if (HAVE_PORTF(fastSignalPin.inout == &PORTF)) {
|
|
DIAG(F("Found PORTF pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTF;
|
|
}
|
|
if (HAVE_PORTG(fastSignalPin.inout == &PORTG)) {
|
|
DIAG(F("Found PORTG pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTG;
|
|
}
|
|
if (HAVE_PORTH(fastSignalPin.inout == &PORTH)) {
|
|
DIAG(F("Found PORTH pin %d"),signalPin);
|
|
fastSignalPin.shadowinout = fastSignalPin.inout;
|
|
fastSignalPin.inout = &shadowPORTH;
|
|
}
|
|
|
|
signalPin2=signal_pin2;
|
|
if (signalPin2!=UNUSED_PIN) {
|
|
dualSignal=true;
|
|
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
|
pinMode(signalPin2, OUTPUT);
|
|
|
|
fastSignalPin2.shadowinout = NULL;
|
|
if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) {
|
|
DIAG(F("Found PORTA pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTA;
|
|
}
|
|
if (HAVE_PORTB(fastSignalPin2.inout == &PORTB)) {
|
|
DIAG(F("Found PORTB pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTB;
|
|
}
|
|
if (HAVE_PORTC(fastSignalPin2.inout == &PORTC)) {
|
|
DIAG(F("Found PORTC pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTC;
|
|
}
|
|
if (HAVE_PORTD(fastSignalPin2.inout == &PORTD)) {
|
|
DIAG(F("Found PORTD pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTD;
|
|
}
|
|
if (HAVE_PORTE(fastSignalPin2.inout == &PORTE)) {
|
|
DIAG(F("Found PORTE pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTE;
|
|
}
|
|
if (HAVE_PORTF(fastSignalPin2.inout == &PORTF)) {
|
|
DIAG(F("Found PORTF pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTF;
|
|
}
|
|
if (HAVE_PORTG(fastSignalPin2.inout == &PORTG)) {
|
|
DIAG(F("Found PORTG pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTG;
|
|
}
|
|
if (HAVE_PORTH(fastSignalPin2.inout == &PORTH)) {
|
|
DIAG(F("Found PORTH pin %d"),signalPin2);
|
|
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
|
fastSignalPin2.inout = &shadowPORTH;
|
|
}
|
|
}
|
|
else dualSignal=false;
|
|
|
|
if (brake_pin!=UNUSED_PIN){
|
|
invertBrake=brake_pin < 0;
|
|
if (invertBrake)
|
|
brake_pin = 0-brake_pin;
|
|
if (brake_pin > MAX_PIN)
|
|
DIAG(F("%S Brake pin %d > %d"), warnString, brake_pin, MAX_PIN);
|
|
brakePin=(byte)brake_pin;
|
|
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
|
|
// if brake is used for railcom cutout we need to do PORTX register trick here as well
|
|
pinMode(brakePin, OUTPUT);
|
|
setBrake(true); // start with brake on in case we hace DC stuff going on
|
|
} else {
|
|
brakePin=UNUSED_PIN;
|
|
}
|
|
|
|
currentPin=current_pin;
|
|
if (currentPin!=UNUSED_PIN) {
|
|
int ret = ADCee::init(currentPin);
|
|
if (ret < -1010) { // XXX give value a name later
|
|
DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin);
|
|
currentPin = UNUSED_PIN;
|
|
}
|
|
}
|
|
senseOffset=0; // value can not be obtained until waveform is activated
|
|
|
|
if (fault_pin != UNUSED_PIN) {
|
|
invertFault=fault_pin < 0;
|
|
if (invertFault)
|
|
fault_pin = 0-fault_pin;
|
|
if (fault_pin > MAX_PIN)
|
|
DIAG(F("%S Fault pin %d > %d"), warnString, fault_pin, MAX_PIN);
|
|
faultPin=(byte)fault_pin;
|
|
DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault);
|
|
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
|
|
pinMode(faultPin, INPUT);
|
|
} else {
|
|
faultPin=UNUSED_PIN;
|
|
}
|
|
|
|
// This conversion performed at compile time so the remainder of the code never needs
|
|
// float calculations or libraray code.
|
|
senseFactorInternal=sense_factor * senseScale;
|
|
tripMilliamps=trip_milliamps;
|
|
#ifdef MAX_CURRENT
|
|
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
|
|
tripMilliamps = MAX_CURRENT;
|
|
#endif
|
|
rawCurrentTripValue=mA2raw(tripMilliamps);
|
|
|
|
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
|
|
// This would mean that the values obtained from the ADC never
|
|
// can reach the trip value. So independent of the current, the
|
|
// short circuit protection would never trip. So we adjust the
|
|
// trip value so that it is tiggered when the ADC reports it's
|
|
// maximum value instead.
|
|
|
|
// DIAG(F("Changing short detection value from %d to %d mA"),
|
|
// raw2mA(rawCurrentTripValue), raw2mA(ADCee::ADCmax()-senseOffset));
|
|
rawCurrentTripValue=ADCee::ADCmax()-senseOffset;
|
|
}
|
|
|
|
if (currentPin==UNUSED_PIN)
|
|
DIAG(F("%S No current or short detection"), warnString);
|
|
else {
|
|
DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue);
|
|
|
|
// self testing diagnostic for the non-float converters... may be removed when happy
|
|
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
|
|
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
|
|
}
|
|
|
|
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
|
}
|
|
|
|
bool MotorDriver::isPWMCapable() {
|
|
return (!dualSignal) && DCCTimer::isPWMPin(signalPin);
|
|
}
|
|
|
|
|
|
void MotorDriver::setPower(POWERMODE mode) {
|
|
if (powerMode == mode) return;
|
|
//DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode);
|
|
lastPowerChange[(int)mode] = micros();
|
|
if (mode == POWERMODE::OVERLOAD)
|
|
globalOverloadStart = lastPowerChange[(int)mode];
|
|
bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT);
|
|
if (on) {
|
|
// when switching a track On, we need to check the crrentOffset with the pin OFF
|
|
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
|
|
senseOffset = ADCee::read(currentPin);
|
|
DIAG(F("Track %c sensOffset=%d"),trackLetter,senseOffset);
|
|
}
|
|
|
|
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
|
if (isProgTrack)
|
|
DCCWaveform::progTrack.clearResets();
|
|
}
|
|
else {
|
|
IODevice::write(powerPin,invertPower ? HIGH : LOW);
|
|
}
|
|
powerMode=mode;
|
|
}
|
|
|
|
// setBrake applies brake if on == true. So to get
|
|
// voltage from the motor bride one needs to do a
|
|
// setBrake(false).
|
|
// If the brakePin is negative that means the sense
|
|
// of the brake pin on the motor bridge is inverted
|
|
// (HIGH == release brake) and setBrake does
|
|
// compensate for that.
|
|
//
|
|
void MotorDriver::setBrake(bool on, bool interruptContext) {
|
|
if (brakePin == UNUSED_PIN) return;
|
|
if (!interruptContext) {noInterrupts();}
|
|
if (on ^ invertBrake)
|
|
setHIGH(fastBrakePin);
|
|
else
|
|
setLOW(fastBrakePin);
|
|
if (!interruptContext) {interrupts();}
|
|
}
|
|
|
|
bool MotorDriver::canMeasureCurrent() {
|
|
return currentPin!=UNUSED_PIN;
|
|
}
|
|
/*
|
|
* Return the current reading as pin reading 0 to max resolution (1024 or 4096).
|
|
* If the fault pin is activated return a negative current to show active fault pin.
|
|
* 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
|
|
* a central value depending on direction.
|
|
*
|
|
* Bool fromISR should be adjusted dependent how function is called
|
|
*/
|
|
int MotorDriver::getCurrentRaw(bool fromISR) {
|
|
(void)fromISR;
|
|
if (currentPin==UNUSED_PIN) return 0;
|
|
int current;
|
|
current = ADCee::read(currentPin, fromISR);
|
|
// here one can diag raw value
|
|
// if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
|
|
current = current-senseOffset; // adjust with offset
|
|
if (current<0) current=0-current;
|
|
// current >= 0 here, we use negative current as fault pin flag
|
|
if ((faultPin != UNUSED_PIN) && powerPin) {
|
|
if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin))
|
|
return (current == 0 ? -1 : -current);
|
|
}
|
|
return current;
|
|
}
|
|
|
|
#ifdef ANALOG_READ_INTERRUPT
|
|
/*
|
|
* This should only be called in interrupt context
|
|
* Copies current value from HW to cached value in
|
|
* Motordriver.
|
|
*/
|
|
#pragma GCC push_options
|
|
#pragma GCC optimize ("-O3")
|
|
bool MotorDriver::sampleCurrentFromHW() {
|
|
byte low, high;
|
|
//if (!bit_is_set(ADCSRA, ADIF))
|
|
if (bit_is_set(ADCSRA, ADSC))
|
|
return false;
|
|
// if ((ADMUX & mask) != (currentPin - A0))
|
|
// return false;
|
|
low = ADCL; //must read low before high
|
|
high = ADCH;
|
|
bitSet(ADCSRA, ADIF);
|
|
sampleCurrent = (high << 8) | low;
|
|
sampleCurrentTimestamp = millis();
|
|
return true;
|
|
}
|
|
void MotorDriver::startCurrentFromHW() {
|
|
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
|
const byte mask = 7;
|
|
#else
|
|
const byte mask = 31;
|
|
#endif
|
|
ADMUX=(1<<REFS0)|((currentPin-A0) & mask); //select AVCC as reference and set MUX
|
|
bitSet(ADCSRA,ADSC); // start conversion
|
|
}
|
|
#pragma GCC pop_options
|
|
#endif //ANALOG_READ_INTERRUPT
|
|
|
|
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
|
#ifdef VARIABLE_TONES
|
|
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
|
247, 262, 294, 330,
|
|
349, 392, 440, 494,
|
|
523, 587, 659, 698,
|
|
494, 440, 392, 249,
|
|
330, 284, 262, 247,
|
|
220, 196, 175, 165 };
|
|
#endif
|
|
#endif
|
|
void MotorDriver::setDCSignal(byte speedcode, uint8_t frequency /*default =0*/) {
|
|
if (brakePin == UNUSED_PIN)
|
|
return;
|
|
// spedcoode is a dcc speed & direction
|
|
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
|
byte tDir=speedcode & 0x80;
|
|
byte brake;
|
|
|
|
if (tSpeed <= 1) brake = 255;
|
|
else if (tSpeed >= 127) brake = 0;
|
|
else brake = 2 * (128-tSpeed);
|
|
|
|
{ // new block because of variable f
|
|
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
|
int f = frequency;
|
|
#ifdef VARIABLE_TONES
|
|
if (tSpeed > 2) {
|
|
if (tSpeed <= 58) {
|
|
f = taurustones[ (tSpeed-2)/2 ] ;
|
|
}
|
|
}
|
|
#endif
|
|
//DIAG(F("Brake pin %d value %d freqency %d"), brakePin, brake, f);
|
|
DCCTimer::DCCEXanalogWrite(brakePin, brake, invertBrake);
|
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency
|
|
#else // all AVR here
|
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, frequency); // frequency steps
|
|
analogWrite(brakePin, invertBrake ? 255-brake : brake);
|
|
#endif
|
|
}
|
|
|
|
//DIAG(F("DCSignal %d"), speedcode);
|
|
if (HAVE_PORTA(fastSignalPin.shadowinout == &PORTA)) {
|
|
noInterrupts();
|
|
HAVE_PORTA(shadowPORTA=PORTA);
|
|
setSignal(tDir);
|
|
HAVE_PORTA(PORTA=shadowPORTA);
|
|
interrupts();
|
|
} else if (HAVE_PORTB(fastSignalPin.shadowinout == &PORTB)) {
|
|
noInterrupts();
|
|
HAVE_PORTB(shadowPORTB=PORTB);
|
|
setSignal(tDir);
|
|
HAVE_PORTB(PORTB=shadowPORTB);
|
|
interrupts();
|
|
} else if (HAVE_PORTC(fastSignalPin.shadowinout == &PORTC)) {
|
|
noInterrupts();
|
|
HAVE_PORTC(shadowPORTC=PORTC);
|
|
setSignal(tDir);
|
|
HAVE_PORTC(PORTC=shadowPORTC);
|
|
interrupts();
|
|
} else if (HAVE_PORTD(fastSignalPin.shadowinout == &PORTD)) {
|
|
noInterrupts();
|
|
HAVE_PORTD(shadowPORTD=PORTD);
|
|
setSignal(tDir);
|
|
HAVE_PORTD(PORTD=shadowPORTD);
|
|
interrupts();
|
|
} else if (HAVE_PORTE(fastSignalPin.shadowinout == &PORTE)) {
|
|
noInterrupts();
|
|
HAVE_PORTE(shadowPORTE=PORTE);
|
|
setSignal(tDir);
|
|
HAVE_PORTE(PORTE=shadowPORTE);
|
|
interrupts();
|
|
} else if (HAVE_PORTF(fastSignalPin.shadowinout == &PORTF)) {
|
|
noInterrupts();
|
|
HAVE_PORTF(shadowPORTF=PORTF);
|
|
setSignal(tDir);
|
|
HAVE_PORTF(PORTF=shadowPORTF);
|
|
interrupts();
|
|
} else if (HAVE_PORTG(fastSignalPin.shadowinout == &PORTG)) {
|
|
noInterrupts();
|
|
HAVE_PORTG(shadowPORTG=PORTG);
|
|
setSignal(tDir);
|
|
HAVE_PORTG(PORTG=shadowPORTG);
|
|
interrupts();
|
|
} else if (HAVE_PORTH(fastSignalPin.shadowinout == &PORTH)) {
|
|
noInterrupts();
|
|
HAVE_PORTH(shadowPORTH=PORTH);
|
|
setSignal(tDir);
|
|
HAVE_PORTH(PORTH=shadowPORTH);
|
|
interrupts();
|
|
} else {
|
|
noInterrupts();
|
|
setSignal(tDir);
|
|
interrupts();
|
|
}
|
|
}
|
|
void MotorDriver::throttleInrush(bool on) {
|
|
if (brakePin == UNUSED_PIN)
|
|
return;
|
|
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT | TRACK_MODE_BOOST)))
|
|
return;
|
|
byte duty = on ? 207 : 0; // duty of 81% at 62500Hz this gives pauses of 3usec
|
|
#if defined(ARDUINO_ARCH_ESP32)
|
|
if(on) {
|
|
DCCTimer::DCCEXInrushControlOn(brakePin, duty, invertBrake);
|
|
} else {
|
|
ledcDetachPin(brakePin); // not DCCTimer::DCCEXledcDetachPin() as we have not
|
|
// registered the pin in the pin to channel array
|
|
}
|
|
#elif defined(ARDUINO_ARCH_STM32)
|
|
if(on) {
|
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
|
DCCTimer::DCCEXanalogWrite(brakePin,duty,invertBrake);
|
|
} else {
|
|
pinMode(brakePin, OUTPUT);
|
|
}
|
|
#else // all AVR here
|
|
if (invertBrake)
|
|
duty = 255-duty;
|
|
if(on){
|
|
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 7); // 7 means max
|
|
}
|
|
analogWrite(brakePin,duty);
|
|
#endif
|
|
}
|
|
unsigned int MotorDriver::raw2mA( int raw) {
|
|
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
|
|
return (int32_t)raw * senseFactorInternal / senseScale;
|
|
}
|
|
unsigned int MotorDriver::mA2raw( unsigned int mA) {
|
|
//DIAG(F("%d = %d * %d / %d"), (int32_t)mA * senseScale / senseFactorInternal, mA, senseScale, senseFactorInternal);
|
|
return (int32_t)mA * senseScale / senseFactorInternal;
|
|
}
|
|
|
|
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
|
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
|
(void) type; // avoid compiler warning if diag not used above.
|
|
#if defined(ARDUINO_ARCH_SAMD)
|
|
PortGroup *port = digitalPinToPort(pin);
|
|
#elif defined(ARDUINO_ARCH_STM32)
|
|
GPIO_TypeDef *port = digitalPinToPort(pin);
|
|
#else
|
|
uint8_t port = digitalPinToPort(pin);
|
|
#endif
|
|
if (input)
|
|
result.inout = portInputRegister(port);
|
|
else
|
|
result.inout = portOutputRegister(port);
|
|
result.maskHIGH = digitalPinToBitMask(pin);
|
|
result.maskLOW = ~result.maskHIGH;
|
|
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////
|
|
// checkPowerOverload(useProgLimit, trackno)
|
|
// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
|
|
// byte trackno: trackmanager knows it's number (could be skipped?)
|
|
//
|
|
// Short ciruit handling strategy:
|
|
//
|
|
// There are the following power states: ON ALERT OVERLOAD OFF
|
|
// OFF state is only changed to/from manually. Power is on
|
|
// during ON and ALERT. Power is off during OVERLOAD and OFF.
|
|
// The overload mechanism changes between the other states like
|
|
//
|
|
// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON
|
|
// or
|
|
// ON -1-> ALERT -4-> ON
|
|
//
|
|
// Times are in class MotorDriver (MotorDriver.h).
|
|
//
|
|
// 1. ON to ALERT:
|
|
// Transition on fault pin condition or current overload
|
|
//
|
|
// 2. ALERT to OVERLOAD:
|
|
// Transition happens if different timeouts have elapsed.
|
|
// If only the fault pin is active, timeout is
|
|
// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms)
|
|
// If only overcurrent is detected, timeout is
|
|
// POWER_SAMPLE_IGNORE_CURRENT (100ms)
|
|
// If fault pin and overcurrent are active, timeout is
|
|
// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms)
|
|
// Transition to OVERLOAD turns off power to the affected
|
|
// output (unless fault pins are shared)
|
|
// If the transition conditions are not fullfilled,
|
|
// transition according to 4 is tested.
|
|
//
|
|
// 3. OVERLOAD to ALERT
|
|
// Transiton happens when timeout has elapsed, timeout
|
|
// is named power_sample_overload_wait. It is started
|
|
// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry
|
|
// to OVERLOAD and then increased by a factor of 2
|
|
// at further entries to the OVERLOAD condition. This
|
|
// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached.
|
|
// power_sample_overload_wait is reset by a poweroff or
|
|
// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON.
|
|
// After timeout power is turned on again and state
|
|
// goes back to ALERT.
|
|
//
|
|
// 4. ALERT to ON
|
|
// Transition happens by watching the current and fault pin
|
|
// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If
|
|
// values have been good during that time, transition is
|
|
// made back to ON. Note that even if state is back to ON,
|
|
// the power_sample_overload_wait time is first reset
|
|
// later (see above).
|
|
//
|
|
// The time keeping is handled by timestamps lastPowerChange[]
|
|
// which are set by each power change and by lastBadSample which
|
|
// keeps track if conditions during ALERT have been good enough
|
|
// to go back to ON. The time differences are calculated by
|
|
// microsSinceLastPowerChange().
|
|
//
|
|
|
|
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
|
|
|
switch (powerMode) {
|
|
|
|
case POWERMODE::OFF: {
|
|
lastPowerMode = POWERMODE::OFF;
|
|
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
|
break;
|
|
}
|
|
|
|
case POWERMODE::ON: {
|
|
lastPowerMode = POWERMODE::ON;
|
|
bool cF = checkFault();
|
|
bool cC = checkCurrent(useProgLimit);
|
|
if(cF || cC ) {
|
|
if (cC) {
|
|
unsigned int mA=raw2mA(lastCurrent);
|
|
DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A',
|
|
cF ? "FAULT" : "",
|
|
mA);
|
|
} else {
|
|
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
|
}
|
|
setPower(POWERMODE::ALERT);
|
|
if ((trackMode & TRACK_MODIFIER_AUTO) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){
|
|
DIAG(F("TRACK %c INVERT"), trackno + 'A');
|
|
invertOutput();
|
|
}
|
|
break;
|
|
}
|
|
// all well
|
|
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
|
|
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case POWERMODE::ALERT: {
|
|
// set local flags that handle how much is output to diag (do not output duplicates)
|
|
bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
|
|
bool powerModeChange = (powerMode != lastPowerMode);
|
|
unsigned long now = micros();
|
|
if (powerModeChange)
|
|
lastBadSample = now;
|
|
lastPowerMode = POWERMODE::ALERT;
|
|
// check how long we have been in this state
|
|
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
|
|
if(checkFault()) {
|
|
throttleInrush(true);
|
|
lastBadSample = now;
|
|
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
|
|
if ( mslpc < timeout) {
|
|
if (powerModeChange)
|
|
DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout);
|
|
break;
|
|
}
|
|
DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait);
|
|
throttleInrush(false);
|
|
setPower(POWERMODE::OVERLOAD);
|
|
break;
|
|
}
|
|
if (checkCurrent(useProgLimit)) {
|
|
lastBadSample = now;
|
|
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
|
|
if (powerModeChange) {
|
|
unsigned int mA=raw2mA(lastCurrent);
|
|
DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA);
|
|
}
|
|
break;
|
|
}
|
|
unsigned int mA=raw2mA(lastCurrent);
|
|
unsigned int maxmA=raw2mA(tripValue);
|
|
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
|
|
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
|
|
throttleInrush(false);
|
|
setPower(POWERMODE::OVERLOAD);
|
|
break;
|
|
}
|
|
// all well
|
|
unsigned long goodtime = micros() - lastBadSample;
|
|
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
|
|
if (true || notFromOverload) { // we did a RESTORE message XXX
|
|
unsigned int mA=raw2mA(lastCurrent);
|
|
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
|
|
}
|
|
throttleInrush(false);
|
|
setPower(POWERMODE::ON);
|
|
break;
|
|
}
|
|
if (goodtime > POWER_SAMPLE_ALERT_GOOD/2) {
|
|
throttleInrush(false);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case POWERMODE::OVERLOAD: {
|
|
lastPowerMode = POWERMODE::OVERLOAD;
|
|
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
|
|
if (mslpc > power_sample_overload_wait) {
|
|
// adjust next wait time
|
|
power_sample_overload_wait *= 2;
|
|
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
|
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
|
#ifdef EXRAIL_ACTIVE
|
|
DIAG(F("Calling EXRAIL"));
|
|
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
|
|
#endif
|
|
// power on test
|
|
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
|
setPower(POWERMODE::ALERT);
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|