diff options
author | David Crocker <dcrocker@eschertech.com> | 2017-11-18 12:36:06 +0300 |
---|---|---|
committer | David Crocker <dcrocker@eschertech.com> | 2017-11-18 12:36:26 +0300 |
commit | e6b7c01e1f0afaac7a6478a6f3634b619f3bc734 (patch) | |
tree | a7d0cf6a2444776910aca66da0c8e212e6e32cfc /src | |
parent | 377154d0eafd289ca2b4548002250c3be0341ab4 (diff) |
Version 1.20beta8
New features:
T R1 activates the tool that was active at the last pause
M915 R2 and R3 actions are now implemented
Extruder heater PWM values are now compensated for supply voltage
Duet WiFi only: M587 and M589 without parameters now report the IP
addresses etc. as well as the SSID (needs DuetWiFiServer 1.20beta9)
When sensorless homing is used on a CoreXY printer, both X and Y motors
are monitored for stalling when homing X or Y. Similarly for CoreXYU
(both X and Y or U and V are monitored).
When using a segmented kinematics such as SCARA, or when long moves are
segmented due to mesh bed compensation, segmented moves can be paused
between segments
M562 with no parameters now clears all heater faults
New debugging module 14 added to report debugging message from the WiFi
module to USB. Use M111 S1 P14 to activate it.
Under voltage and over voltage events are now logged
Overheating drivers are now logged
M81 power off commands are now logged
Bug fixes:
Workaround for SX1509B chip problem: if an analog write was performed to
a pin on an SX1509B device, subsequent digital writes and pinMode calls
didn't work
When a print was resumed after power failure, the amount of extrusion
during the initial partial move was incorrect
Diffstat (limited to 'src')
35 files changed, 849 insertions, 419 deletions
diff --git a/src/Configuration.h b/src/Configuration.h index c6b9754f..d75c50d7 100644 --- a/src/Configuration.h +++ b/src/Configuration.h @@ -71,11 +71,12 @@ constexpr float HOT_ENOUGH_TO_RETRACT = 90.0; // Celsius constexpr uint8_t MAX_BAD_TEMPERATURE_COUNT = 4; // Number of bad temperature samples permitted before a heater fault is reported constexpr float BAD_LOW_TEMPERATURE = -10.0; // Celsius -constexpr float DefaultExtruderTemperatureLimit = 288.0; // Celsius - E3D say to tighten the hot end at 285C +constexpr float DefaultExtruderTemperatureLimit = 290.0; // Celsius - E3D say to tighten the hot end at 285C constexpr float DefaultBedTemperatureLimit = 125.0; // Celsius constexpr float HOT_END_FAN_TEMPERATURE = 45.0; // Temperature at which a thermostatic hot end fan comes on constexpr float ThermostatHysteresis = 1.0; // How much hysteresis we use to prevent noise turning fans on/off too often constexpr float BAD_ERROR_TEMPERATURE = 2000.0; // Must exceed any reasonable 5temperature limit including DEFAULT_TEMPERATURE_LIMIT +constexpr uint32_t DefaultHeaterFaultTimeout = 10 * 60 * 1000; // How long we wait (in milliseconds) for user intervention after a heater fault before shutting down // Heating model default parameters. For the chamber heater, we use the same values as for the bed heater. // These parameters are about right for an E3Dv6 hot end with 30W heater. diff --git a/src/DuetNG/DueXn.cpp b/src/DuetNG/DueXn.cpp index 8f35a223..4eb80210 100644 --- a/src/DuetNG/DueXn.cpp +++ b/src/DuetNG/DueXn.cpp @@ -56,23 +56,23 @@ namespace DuetExpansion // Identify which expansion board (if any) is attached and initialise it ExpansionBoardType DueXnInit() { - uint8_t ret = dueXnExpander.begin(DueXnAddress); - if (ret != 1) + bool ret = dueXnExpander.begin(DueXnAddress); + if (!ret) { delay(100); // wait a little while ret = dueXnExpander.begin(DueXnAddress); // do 1 retry } - if (ret != 1) - { - dueXnBoardType = ExpansionBoardType::none; // no device found at that address, or a serious error - } - else + if (ret) { dueXnExpander.pinModeMultiple(BoardTypePins, INPUT_PULLUP); const uint16_t data = dueXnExpander.digitalReadAll(); dueXnBoardType = boardTypes[(data & BoardTypePins) >> BoardTypeShift]; } + else + { + dueXnBoardType = ExpansionBoardType::none; // no device found at that address, or a serious error + } if (dueXnBoardType != ExpansionBoardType::none) { @@ -98,14 +98,14 @@ namespace DuetExpansion // Look for an additional output pin expander void AdditionalOutputInit() { - uint8_t ret = additionalIoExpander.begin(AdditionalIoExpanderAddress); - if (ret != 1) + bool ret = additionalIoExpander.begin(AdditionalIoExpanderAddress); + if (!ret) { delay(100); // wait a little while ret = additionalIoExpander.begin(AdditionalIoExpanderAddress); // do 1 retry } - if (ret == 1) + if (ret) { additionalIoExpander.pinModeMultiple((1u << 16) - 1, INPUT_PULLDOWN); additionalIoInputBits = additionalIoExpander.digitalReadAll(); @@ -173,7 +173,7 @@ namespace DuetExpansion break; case INPUT_PULLUP: case INPUT_PULLDOWN: - mode = INPUT; // we are using 5rV-tolerant input with external pullup resistors, so don't enable internal pullup/pulldown resistors + mode = INPUT; // we are using 5V-tolerant input with external pullup resistors, so don't enable internal pullup/pulldown resistors break; default: break; diff --git a/src/DuetNG/DuetWiFi/Network.cpp b/src/DuetNG/DuetWiFi/Network.cpp index c776fc7c..70032b99 100644 --- a/src/DuetNG/DuetWiFi/Network.cpp +++ b/src/DuetNG/DuetWiFi/Network.cpp @@ -103,8 +103,8 @@ static inline void DisableEspInterrupt() // WiFi interface class Network::Network(Platform& p) : platform(p), nextResponderToPoll(nullptr), uploader(nullptr), currentSocket(0), ftpDataPort(0), - state(NetworkState::disabled), requestedMode(WiFiState::disabled), currentMode(WiFiState::disabled), activated(false), serialRunning(false), - espStatusChanged(false), spiTxUnderruns(0), spiRxOverruns(0) + state(NetworkState::disabled), requestedMode(WiFiState::disabled), currentMode(WiFiState::disabled), activated(false), + espStatusChanged(false), spiTxUnderruns(0), spiRxOverruns(0), serialRunning(false) { for (size_t i = 0; i < NumProtocols; ++i) { @@ -476,7 +476,6 @@ void Network::Spin(bool full) { if (espStatusChanged && digitalRead(EspTransferRequestPin)) { - Platform::softwareResetDebugInfo = 1; if (reprap.Debug(moduleNetwork)) { debugPrintf("ESP reported status change\n"); @@ -489,7 +488,6 @@ void Network::Spin(bool full) && currentMode != WiFiState::autoReconnecting ) { - Platform::softwareResetDebugInfo = 2; // Tell the wifi module to change mode int32_t rslt = ResponseUnknownError; if (currentMode != WiFiState::idle) @@ -518,7 +516,6 @@ void Network::Spin(bool full) } else if (currentMode == WiFiState::connected || currentMode == WiFiState::runningAsAccessPoint) { - Platform::softwareResetDebugInfo = 3; // Find the next socket to poll const size_t startingSocket = currentSocket; do @@ -553,7 +550,6 @@ void Network::Spin(bool full) { nr = responders; // 'responders' can't be null at this point } - Platform::softwareResetDebugInfo = 4; doneSomething = nr->Spin(); nr = nr->GetNext(); } while (!doneSomething && nr != nextResponderToPoll); @@ -561,16 +557,6 @@ void Network::Spin(bool full) } } } - else if (currentMode == requestedMode && (currentMode == WiFiState::connected || currentMode == WiFiState::runningAsAccessPoint)) - { - Platform::softwareResetDebugInfo = 5; - sockets[currentSocket].Poll(full); - ++currentSocket; - if (currentSocket == NumTcpSockets) - { - currentSocket = 0; - } - } break; case NetworkState::changingMode: @@ -614,9 +600,40 @@ void Network::Spin(bool full) break; } - Platform::softwareResetDebugInfo = 0; + // Check for debug info received from the WiFi module + if (serialRunning) + { + while (!debugPrintPending && Serial1.available() != 0) + { + const char c = (char)Serial1.read(); + if (c == '\n') + { + debugPrintPending = true; + } + else if (c != '\r') + { + const size_t len = debugMessageBuffer.cat(c); + if (len == debugMessageBuffer.MaxLength()) + { + debugPrintPending = true; + } + } + } + } + if (full) { + // Check for debug info received from the WiFi module + if (debugPrintPending) + { + if (reprap.Debug(moduleWiFi)) + { + debugPrintf("WiFi: %s\n", debugMessageBuffer.Pointer()); + } + debugMessageBuffer.Clear(); + debugPrintPending = false; + } + platform.ClassReport(longWait); } } @@ -1385,8 +1402,10 @@ void Network::StartWiFi() { digitalWrite(EspResetPin, HIGH); ConfigurePin(g_APinDescription[APINS_UART1]); // connect the pins to UART1 - Serial1.begin(115200); // initialise the UART, to receive debug info + Serial1.begin(WiFiBaudRate); // initialise the UART, to receive debug info + debugMessageBuffer.Clear(); serialRunning = true; + debugPrintPending = false; } // Reset the ESP8266 and leave held in reset diff --git a/src/DuetNG/DuetWiFi/Network.h b/src/DuetNG/DuetWiFi/Network.h index c2081895..de20b7cf 100644 --- a/src/DuetNG/DuetWiFi/Network.h +++ b/src/DuetNG/DuetWiFi/Network.h @@ -147,7 +147,6 @@ private: WiFiState requestedMode; WiFiState currentMode; bool activated; - bool serialRunning; volatile bool espStatusChanged; uint8_t ipAddress[4]; @@ -165,6 +164,11 @@ private: unsigned int responseTimeoutCount; char wiFiServerVersion[16]; + + // For processing debug messages from the WiFi module + bool serialRunning; + bool debugPrintPending; + String<100> debugMessageBuffer; }; #endif diff --git a/src/DuetNG/SX1509.cpp b/src/DuetNG/SX1509.cpp index d2e628bf..73ac5c3e 100644 --- a/src/DuetNG/SX1509.cpp +++ b/src/DuetNG/SX1509.cpp @@ -26,12 +26,11 @@ Distributed as-is; no warranty is given. #include "SX1509.h" #include "SX1509Registers.h" -SX1509::SX1509() +SX1509::SX1509() : _clkX(0) { - _clkX = 0; } -uint8_t SX1509::begin(uint8_t address) +bool SX1509::begin(uint8_t address) { // Store the received parameters into member variables deviceAddress = address; @@ -41,20 +40,18 @@ uint8_t SX1509::begin(uint8_t address) reset(); - // Communication test. We'll read from two registers with different - // default values to verify communication. - unsigned int testRegisters = 0; - testRegisters = readWord(REG_INTERRUPT_MASK_A); // this should return 0xFF00 + pwmPins = 0; - // Then read a byte that should be 0x00 - if (testRegisters == 0xFF00) + // Communication test. We'll read from two registers with different default values to verify communication. + const uint16_t testRegisters = readWord(REG_INTERRUPT_MASK_A); // this should return 0xFF00 + const bool ok = (testRegisters == 0xFF00); + if (ok) { clock(DefaultOscDivider); - writeWord(REG_HIGH_INPUT_B, 0xFFFF); // set all inputs to be 5V-tolerant - return 1; + writeWord(REG_HIGH_INPUT_B, 0xFFFF); // set all inputs to be 5V-tolerant } - return 0; + return ok; } void SX1509::reset() @@ -66,15 +63,32 @@ void SX1509::reset() void SX1509::setBitsInWord(uint8_t registerAddress, uint16_t bits) { - const uint16_t regVal = readWord(registerAddress); - writeWord(registerAddress, regVal | bits); + if (bits != 0) + { + const uint16_t regVal = readWord(registerAddress); + writeWord(registerAddress, regVal | bits); + } } void SX1509::clearBitsInWord(uint8_t registerAddress, uint16_t bits) { - const uint16_t regVal = readWord(registerAddress); - writeWord(registerAddress, regVal & (~bits)); + if (bits != 0) + { + const uint16_t regVal = readWord(registerAddress); + writeWord(registerAddress, regVal & (~bits)); + } +} +void SX1509::analogWriteMultiple(uint16_t pins, uint8_t pwm) +{ + for (uint8_t pin = 0; pins != 0; ++pin) + { + if ((pins & 1u) != 0) + { + analogWrite(pin, pwm); + } + pins >>= 1; + } } void SX1509::pinMode(uint8_t pin, PinMode inOut) @@ -82,12 +96,14 @@ void SX1509::pinMode(uint8_t pin, PinMode inOut) pinModeMultiple(1u << pin, inOut); } +// Set the pin mode for multiple pins. +// Once we have enabled LED driver mode, disabling it doesn't seem to work. +// So we track which pins are in PWM (i.e. LED driver) mode, and we never try to switch them back to being ordinary outputs. void SX1509::pinModeMultiple(uint16_t pins, PinMode inOut) { switch (inOut) { case INPUT: - clearBitsInWord(REG_LED_DRIVER_ENABLE_B, pins); clearBitsInWord(REG_INPUT_DISABLE_B, pins); setBitsInWord(REG_DIR_B, pins); clearBitsInWord(REG_PULL_UP_B, pins); @@ -95,7 +111,6 @@ void SX1509::pinModeMultiple(uint16_t pins, PinMode inOut) break; case INPUT_PULLUP: - clearBitsInWord(REG_LED_DRIVER_ENABLE_B, pins); clearBitsInWord(REG_INPUT_DISABLE_B, pins); setBitsInWord(REG_DIR_B, pins); clearBitsInWord(REG_PULL_DOWN_B, pins); @@ -103,7 +118,6 @@ void SX1509::pinModeMultiple(uint16_t pins, PinMode inOut) break; case INPUT_PULLDOWN: - clearBitsInWord(REG_LED_DRIVER_ENABLE_B, pins); clearBitsInWord(REG_INPUT_DISABLE_B, pins); setBitsInWord(REG_DIR_B, pins); clearBitsInWord(REG_PULL_UP_B, pins); @@ -111,39 +125,39 @@ void SX1509::pinModeMultiple(uint16_t pins, PinMode inOut) break; case OUTPUT_LOW: - clearBitsInWord(REG_LED_DRIVER_ENABLE_B, pins); clearBitsInWord(REG_PULL_UP_B, pins); clearBitsInWord(REG_PULL_DOWN_B, pins); - clearBitsInWord(REG_DATA_B, pins); + clearBitsInWord(REG_DATA_B, pins & ~pwmPins); clearBitsInWord(REG_OPEN_DRAIN_B, pins); clearBitsInWord(REG_DIR_B, pins); + analogWriteMultiple(pins & pwmPins, 0); break; case OUTPUT_HIGH: - clearBitsInWord(REG_LED_DRIVER_ENABLE_B, pins); clearBitsInWord(REG_PULL_UP_B, pins); clearBitsInWord(REG_PULL_DOWN_B, pins); - setBitsInWord(REG_DATA_B, pins); + setBitsInWord(REG_DATA_B, pins & ~pwmPins); clearBitsInWord(REG_OPEN_DRAIN_B, pins); clearBitsInWord(REG_DIR_B, pins); + analogWriteMultiple(pins & pwmPins, 255); break; case OUTPUT_LOW_OPEN_DRAIN: - clearBitsInWord(REG_LED_DRIVER_ENABLE_B, pins); clearBitsInWord(REG_PULL_UP_B, pins); clearBitsInWord(REG_PULL_DOWN_B, pins); - clearBitsInWord(REG_DATA_B, pins); + clearBitsInWord(REG_DATA_B, pins & ~pwmPins); setBitsInWord(REG_OPEN_DRAIN_B, pins); clearBitsInWord(REG_DIR_B, pins); + analogWriteMultiple(pins & pwmPins, 0); break; case OUTPUT_HIGH_OPEN_DRAIN: - clearBitsInWord(REG_LED_DRIVER_ENABLE_B, pins); clearBitsInWord(REG_PULL_UP_B, pins); clearBitsInWord(REG_PULL_DOWN_B, pins); - setBitsInWord(REG_DATA_B, pins); + setBitsInWord(REG_DATA_B, pins & ~pwmPins); setBitsInWord(REG_OPEN_DRAIN_B, pins); clearBitsInWord(REG_DIR_B, pins); + analogWriteMultiple(pins & pwmPins, 255); break; case OUTPUT_PWM_LOW: @@ -162,7 +176,11 @@ void SX1509::pinModeMultiple(uint16_t pins, PinMode inOut) void SX1509::digitalWrite(uint8_t pin, bool highLow) { - if (highLow) + if (((1u << pin) & pwmPins) != 0) + { + analogWrite(pin, (highLow) ? 255 : 0); + } + else if (highLow) { setBitsInWord(REG_DATA_B, 1u << pin); } @@ -189,11 +207,15 @@ uint16_t SX1509::digitalReadAll() return readWord(REG_DATA_B); } +#if 0 // unused + void SX1509::ledDriverInit(uint8_t pin, bool log, bool openDrain) { ledDriverInitMultiple(1u << pin, log, openDrain); } +#endif + void SX1509::ledDriverInitMultiple(uint16_t pins, bool log, bool openDrain) { if (openDrain) @@ -208,7 +230,6 @@ void SX1509::ledDriverInitMultiple(uint16_t pins, bool log, bool openDrain) clearBitsInWord(REG_PULL_UP_B, pins); // disable pullup clearBitsInWord(REG_PULL_DOWN_B, pins); // disable pulldown clearBitsInWord(REG_DIR_B, pins); // set as an output - // Configure LED driver clock and mode (REG_MISC) uint8_t tempByte = readByte(REG_MISC); @@ -227,6 +248,8 @@ void SX1509::ledDriverInitMultiple(uint16_t pins, bool log, bool openDrain) // Set REG_DATA bit low ~ LED driver started clearBitsInWord(REG_DATA_B, pins); + + pwmPins |= pins; // record which pins are in LED driver mode } void SX1509::analogWrite(uint8_t pin, uint8_t iOn) @@ -239,6 +262,8 @@ void SX1509::analogWrite(uint8_t pin, uint8_t iOn) writeByte(REG_I_ON[pin], ~iOn); } +#if 0 // these functions are not used + void SX1509::blink(uint8_t pin, unsigned long tOn, unsigned long tOff, uint8_t onIntensity, uint8_t offIntensity) { const uint8_t onReg = calculateLEDTRegister(tOn); @@ -489,6 +514,8 @@ void SX1509::debounceKeypad(uint8_t time, uint8_t numRows, uint8_t numCols) } } +#endif + void SX1509::enableInterrupt(uint8_t pin, uint8_t riseFall) { enableInterruptMultiple(1u << pin, riseFall); @@ -650,7 +677,7 @@ uint16_t SX1509::readWord(uint8_t registerAddress) Wire.endTransmission(); Wire.requestFrom(deviceAddress, (uint8_t) 2); - while ((Wire.available() < 2) && (timeout != 0)) + while (Wire.available() < 2 && timeout != 0) { timeout--; } @@ -660,8 +687,8 @@ uint16_t SX1509::readWord(uint8_t registerAddress) return 0; } - uint16_t msb = (Wire.read() & 0x00FF) << 8; - uint16_t lsb = (Wire.read() & 0x00FF); + const uint16_t msb = (Wire.read() & 0x00FF) << 8; + const uint16_t lsb = (Wire.read() & 0x00FF); return msb | lsb; } @@ -772,7 +799,7 @@ void SX1509::writeBytes(uint8_t firstRegisterAddress, uint8_t * writeArray, uint { Wire.beginTransmission(deviceAddress); Wire.write(firstRegisterAddress); - for (uint8_t i=0; i < length; i++) + for (uint8_t i = 0; i < length; i++) { Wire.write(writeArray[i]); } diff --git a/src/DuetNG/SX1509.h b/src/DuetNG/SX1509.h index d4a0fc53..6a4653e6 100644 --- a/src/DuetNG/SX1509.h +++ b/src/DuetNG/SX1509.h @@ -36,6 +36,7 @@ private: // Misc variables: uint32_t _clkX; + uint16_t pwmPins; // bitmap of pins configured as PWM output pins // Read Functions: uint8_t readByte(uint8_t registerAddress); @@ -50,6 +51,7 @@ private: void writeBytes(uint8_t firstRegisterAddress, uint8_t * writeArray, uint8_t length); void setBitsInWord(uint8_t registerAddress, uint16_t bits); void clearBitsInWord(uint8_t registerAddress, uint16_t bits); + void analogWriteMultiple(uint16_t pins, uint8_t pwm); // Helper functions: @@ -76,10 +78,10 @@ public: // - address: should be the 7-bit address of the SX1509. This should be // one of four values - 0x3E, 0x3F, 0x70, 0x71 - all depending on what the // ADDR0 and ADDR1 pins are set to. This variable is required. - // Output: Returns a 1 if communication is successful, 0 on error. + // Output: Returns true if communication is successful, false on error. // ----------------------------------------------------------------------------- - uint8_t begin(uint8_t address); - + bool begin(uint8_t address); + // ----------------------------------------------------------------------------- // reset(): This function resets the SX1509. A software // reset writes a 0x12 then 0x34 to the REG_RESET as outlined in the @@ -97,7 +99,7 @@ public: // inOut parameter. They do what they say! // ----------------------------------------------------------------------------- void pinMode(uint8_t pin, PinMode inOut); - + // pinModeMultiple(uint16_t pins, PinMode inOut): This function sets several of the SX1509's 16 // outputs to either an INPUT or OUTPUT. // @@ -130,12 +132,13 @@ public: // This function returns true if HIGH, false if LOW // ----------------------------------------------------------------------------- bool digitalRead(uint8_t pin); - + // ----------------------------------------------------------------------------- // digitalReadAll(): This function reads all 16 pins. // ----------------------------------------------------------------------------- uint16_t digitalReadAll(); +#if 0 // unused // ----------------------------------------------------------------------------- // ledDriverInit(uint8_t pin, bool log): This function initializes LED // driving on a pin. It must be called if you want to use the pwm or blink @@ -148,7 +151,8 @@ public: // - currently log sets both bank A and B to the same mode // ----------------------------------------------------------------------------- void ledDriverInit(uint8_t pin, bool log, bool openDrain); - +#endif + // ----------------------------------------------------------------------------- // ledDriverInitMultiple(uint16_t pins, bool log): This function initializes LED // driving on a pin. It must be called if you want to use the pwm or blink @@ -174,7 +178,9 @@ public: // Note: ledDriverInit should be called on the pin before calling this. // ----------------------------------------------------------------------------- void analogWrite(uint8_t pin, uint8_t iOn); - + +#if 0 // these functions are not used + // ----------------------------------------------------------------------------- // setupBlink(uint8_t pin, uint8_t tOn, uint8_t tOff, uint8_t offIntensity, uint8_t tRise, uint8_t // tFall): blink performs both the blink and breath LED driver functions. @@ -352,6 +358,8 @@ public: // ----------------------------------------------------------------------------- void debounceKeypad(uint8_t time, uint8_t numRows, uint8_t numCols); +#endif + // ----------------------------------------------------------------------------- // enableInterrupt(uint8_t pin, uint8_t riseFall): This function sets up an interrupt // on a pin. Interrupts can occur on all SX1509 pins, and can be generated @@ -370,7 +378,7 @@ public: // pull-up/down resistors! Do that before (or after). // ----------------------------------------------------------------------------- void enableInterrupt(uint8_t pin, uint8_t riseFall); - + // ----------------------------------------------------------------------------- // enableInterruptMultiple(uint16_t pins, uint8_t riseFall): This function sets up an interrupt // on a pin. Interrupts can occur on all SX1509 pins, and can be generated diff --git a/src/GCodes/GCodes.cpp b/src/GCodes/GCodes.cpp index dfb93a62..e5a25f75 100644 --- a/src/GCodes/GCodes.cpp +++ b/src/GCodes/GCodes.cpp @@ -110,9 +110,14 @@ void GCodes::Init() retractSpeed = unRetractSpeed = DefaultRetractSpeed * SecondsToMinutes; isRetracted = false; lastAuxStatusReportType = -1; // no status reports requested yet + spindleMaxRpm = DefaultMaxSpindleRpm; laserMaxPower = DefaultMaxLaserPower; + heaterFaultState = HeaterFaultState::noFault; + heaterFaultTime = 0; + heaterFaultTimeout = DefaultHeaterFaultTimeout; + #if SUPPORT_SCANNER reprap.GetScanner().SetGCodeBuffer(serialGCode); #endif @@ -260,6 +265,7 @@ void GCodes::Spin() } CheckTriggers(); + CheckHeaterFault(); CheckFilament(); // Get the GCodeBuffer that we want to process a command from. Give priority to auto-pause. @@ -583,6 +589,7 @@ void GCodes::RunStateMachine(GCodeBuffer& gb, StringRef& reply) virtualExtruderPosition = pauseRestorePoint.virtualExtruderPosition; // reset the extruder position in case we are receiving absolute extruder moves moveBuffer.virtualExtruderPosition = pauseRestorePoint.virtualExtruderPosition; fileGCode->MachineState().feedrate = pauseRestorePoint.feedRate; + moveFractionToSkip = pauseRestorePoint.proportionDone; isPaused = false; reply.copy("Printing resumed"); platform.Message(LogMessage, "Printing resumed\n"); @@ -640,7 +647,7 @@ void GCodes::RunStateMachine(GCodeBuffer& gb, StringRef& reply) { reprap.StandbyTool(tool->Number()); } - reprap.GetHeat().SwitchOffAll(); + reprap.GetHeat().SwitchOffAll(true); } // chrishamm 2014-18-10: Although RRP says M0 is supposed to turn off all drives and heaters, @@ -1203,7 +1210,7 @@ void GCodes::RunStateMachine(GCodeBuffer& gb, StringRef& reply) } else { - SaveResumeInfo(true); // create the resume file so that we can resume after power down + SaveResumeInfo(true); // create the resume file so that we can resume after power down platform.Message(LoggedGenericMessage, "Print auto-paused due to low voltage\n"); gb.SetState(GCodeState::normal); } @@ -1419,7 +1426,8 @@ void GCodes::CheckFilament() && LockMovement(*autoPauseGCode) // need to lock movement before executing the pause macro ) { - DoPause(*autoPauseGCode, PauseReason::filament, "Extruder %u reports %s", lastFilamentErrorExtruder, FilamentSensor::GetErrorMessage(lastFilamentError)); + scratchString.printf("Extruder %u reports %s", lastFilamentErrorExtruder, FilamentSensor::GetErrorMessage(lastFilamentError)); + DoPause(*autoPauseGCode, PauseReason::filament, scratchString.Pointer()); lastFilamentError = FilamentSensorStatus::ok; } } @@ -1443,7 +1451,7 @@ void GCodes::DoEmergencyStop() } // Pause the print. Before calling this, check that we are doing a file print that isn't already paused and get the movement lock. -void GCodes::DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg, ...) +void GCodes::DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg) { if (&gb == fileGCode) { @@ -1464,13 +1472,10 @@ void GCodes::DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg, ...) else if (segmentsLeft != 0 && moveBuffer.canPauseBefore) { // We were not able to skip any moves, however we can skip the move that is waiting - ToolOffsetInverseTransform(moveBuffer.initialCoords, currentUserPosition); - pauseRestorePoint.feedRate = moveBuffer.feedRate; pauseRestorePoint.virtualExtruderPosition = moveBuffer.virtualExtruderPosition; pauseRestorePoint.filePos = moveBuffer.filePos; -#if SUPPORT_IOBITS - pauseRestorePoint.ioBits = moveBuffer.ioBits; -#endif + pauseRestorePoint.feedRate = moveBuffer.feedRate; + ToolOffsetInverseTransform(pauseRestorePoint.moveCoords, currentUserPosition); // transform the returned coordinates to user coordinates ClearMove(); } else @@ -1511,6 +1516,8 @@ void GCodes::DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg, ...) } SaveFanSpeeds(); + pauseRestorePoint.toolNumber = reprap.GetCurrentToolNumber(); + if (simulationMode == 0) { SaveResumeInfo(false); // create the resume file so that we can resume after power down @@ -1521,11 +1528,7 @@ void GCodes::DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg, ...) if (msg != nullptr) { - va_list vargs; - va_start(vargs, msg); - scratchString.vprintf(msg, vargs); - va_end(vargs); - platform.SendAlert(GenericMessage, scratchString.Pointer(), "Printing paused", 1, 0.0, 0); + platform.SendAlert(GenericMessage, msg, "Printing paused", 1, 0.0, 0); } } @@ -1580,6 +1583,69 @@ bool GCodes::IsReallyPrinting() const return reprap.GetPrintMonitor().IsPrinting() && IsRunning(); } +#if HAS_VOLTAGE_MONITOR || HAS_SMART_DRIVERS + +// Do an emergency pause following loss of power or a motor stall returning true if successful, false if needs to be retried +bool GCodes::DoEmergencyPause() +{ + if (!autoPauseGCode->IsCompletelyIdle()) + { + return false; // we can't pause if the auto pause thread is busy already + } + + // Save the resume info, stop movement immediately and run the low voltage pause script to lift the nozzle etc. + GrabMovement(*autoPauseGCode); + + const bool movesSkipped = reprap.GetMove().LowPowerPause(pauseRestorePoint); + if (movesSkipped) + { + // The PausePrint call has filled in the restore point with machine coordinates + ToolOffsetInverseTransform(pauseRestorePoint.moveCoords, currentUserPosition); // transform the returned coordinates to user coordinates + ClearMove(); + } + else if (segmentsLeft != 0 && moveBuffer.filePos != noFilePosition) + { + // We were not able to skip any moves, however we can skip the remaining segments of this current move + ToolOffsetInverseTransform(moveBuffer.initialCoords, currentUserPosition); + pauseRestorePoint.feedRate = moveBuffer.feedRate; + pauseRestorePoint.virtualExtruderPosition = moveBuffer.virtualExtruderPosition; + pauseRestorePoint.filePos = moveBuffer.filePos; + pauseRestorePoint.proportionDone = (float)(totalSegments - segmentsLeft)/(float)totalSegments; +#if SUPPORT_IOBITS + pauseRestorePoint.ioBits = moveBuffer.ioBits; +#endif + ClearMove(); + } + else + { + // We were not able to skip any moves, and if there is a move waiting then we can't skip that one either + pauseRestorePoint.feedRate = fileGCode->MachineState().feedrate; + pauseRestorePoint.virtualExtruderPosition = virtualExtruderPosition; + + // TODO: when we use RTOS there is a possible race condition in the following, + // because we might try to pause when a waiting move has just been added but before the gcode buffer has been re-initialised ready for the next command + pauseRestorePoint.filePos = fileGCode->GetFilePosition(fileInput->BytesCached()); + pauseRestorePoint.proportionDone = 0.0; +#if SUPPORT_IOBITS + pauseRestorePoint.ioBits = moveBuffer.ioBits; +#endif + } + + // Replace the paused machine coordinates by user coordinates, which we updated earlier + for (size_t axis = 0; axis < numVisibleAxes; ++axis) + { + pauseRestorePoint.moveCoords[axis] = currentUserPosition[axis]; + } + + SaveFanSpeeds(); + pauseRestorePoint.toolNumber = reprap.GetCurrentToolNumber(); + isPaused = true; + + return true; +} + +#endif + #if HAS_VOLTAGE_MONITOR // Try to pause the current SD card print, returning true if successful, false if needs to be called again @@ -1590,7 +1656,7 @@ bool GCodes::LowVoltagePause() return true; // ignore the low voltage indication } - reprap.GetHeat().SuspendHeaters(true); // turn heaters off to conserve power for the motors to execute the pause + reprap.GetHeat().SuspendHeaters(true); // turn the heaters off to conserve power for the motors to execute the pause if (IsResuming()) { // This is an unlucky situation, because the resume macro is probably being run, which will probably lower the head back on to the print. @@ -1613,64 +1679,17 @@ bool GCodes::LowVoltagePause() if (reprap.GetPrintMonitor().IsPrinting()) { - if (!autoPauseGCode->IsCompletelyIdle()) - { - return false; // we can't pause if the auto pause thread is busy already - } - - // Save the resume info, stop movement immediately and run the low voltage pause script to lift the nozzle etc. - GrabMovement(*autoPauseGCode); - - const bool movesSkipped = reprap.GetMove().LowPowerPause(pauseRestorePoint); - if (movesSkipped) - { - // The PausePrint call has filled in the restore point with machine coordinates - ToolOffsetInverseTransform(pauseRestorePoint.moveCoords, currentUserPosition); // transform the returned coordinates to user coordinates - ClearMove(); - } - else if (segmentsLeft != 0 && moveBuffer.filePos != noFilePosition) - { - // We were not able to skip any moves, however we can skip the remaining segments of this current move - ToolOffsetInverseTransform(moveBuffer.initialCoords, currentUserPosition); - pauseRestorePoint.feedRate = moveBuffer.feedRate; - pauseRestorePoint.virtualExtruderPosition = moveBuffer.virtualExtruderPosition; - pauseRestorePoint.filePos = moveBuffer.filePos; - pauseRestorePoint.proportionDone = (float)(totalSegments - segmentsLeft)/(float)totalSegments; -#if SUPPORT_IOBITS - pauseRestorePoint.ioBits = moveBuffer.ioBits; -#endif - ClearMove(); - } - else + if (!DoEmergencyPause()) { - // We were not able to skip any moves, and if there is a move waiting then we can't skip that one either - pauseRestorePoint.feedRate = fileGCode->MachineState().feedrate; - pauseRestorePoint.virtualExtruderPosition = virtualExtruderPosition; - - // TODO: when we use RTOS there is a possible race condition in the following, - // because we might try to pause when a waiting move has just been added but before the gcode buffer has been re-initialised ready for the next command - pauseRestorePoint.filePos = fileGCode->GetFilePosition(fileInput->BytesCached()); - pauseRestorePoint.proportionDone = 0.0; -#if SUPPORT_IOBITS - pauseRestorePoint.ioBits = moveBuffer.ioBits; -#endif - } - - // Replace the paused machine coordinates by user coordinates, which we updated earlier - for (size_t axis = 0; axis < numVisibleAxes; ++axis) - { - pauseRestorePoint.moveCoords[axis] = currentUserPosition[axis]; + return false; } - SaveFanSpeeds(); - // Run the auto-pause script if (powerFailScript != nullptr) { autoPauseGCode->Put(powerFailScript); } autoPauseGCode->SetState(GCodeState::powerFailPausing1); - isPaused = true; isPowerFailPaused = true; // Don't do any more here, we want the auto pause thread to run as soon as possible @@ -1683,22 +1702,60 @@ bool GCodes::LowVoltagePause() // If the pause was short enough, resume automatically. bool GCodes::LowVoltageResume() { - reprap.GetHeat().SuspendHeaters(false); // turn heaters off to conserve power for the motors to execute the pause + reprap.GetHeat().SuspendHeaters(false); // turn the heaters on again if (isPaused && isPowerFailPaused) { + isPowerFailPaused = false; // pretend it's a normal pause // Run resurrect.g automatically //TODO qq; - platform.Message(LoggedGenericMessage, "Print auto-resumed\n"); + //platform.Message(LoggedGenericMessage, "Print auto-resumed\n"); + } + return true; +} + +#endif + +#if HAS_SMART_DRIVERS + +// Pause the print because the specified driver has reported a stall +bool GCodes::PauseOnStall(DriversBitmap stalledDrivers) +{ + if (!IsReallyPrinting()) + { + return true; // if not printing, acknowledge it but take no action + } + if (!autoPauseGCode->IsCompletelyIdle()) + { + return false; // can't handle it yet } + if (!LockMovement(*autoPauseGCode)) + { + return false; + } + + scratchString.printf("Stall detected on driver(s)"); + ListDrivers(scratchString, stalledDrivers); + DoPause(*autoPauseGCode, PauseReason::stall, scratchString.Pointer()); return true; } -// Permit printing to be resumed after it has been suspended -bool GCodes::AutoResumeAfterShutdown() +// Re-home and resume the print because the specified driver has reported a stall +bool GCodes::ReHomeOnStall(DriversBitmap stalledDrivers) { - // Currently we don't do anything here + if (!IsReallyPrinting()) + { + return true; // if not printing, acknowledge it but take no action + } + if (!DoEmergencyPause()) + { + return false; // can't handle it yet + } + + autoPauseGCode->SetState(GCodeState::resuming1); // set up to resume after rehoming + DoFileMacro(*autoPauseGCode, REHOME_G, true); // run the SD card rehome-and-resume script return true; } + #endif void GCodes::SaveResumeInfo(bool wasPowerFailure) @@ -1989,12 +2046,9 @@ bool GCodes::DoStraightMove(GCodeBuffer& gb, StringRef& reply, bool isCoordinate moveBuffer.isCoordinated = isCoordinated; moveBuffer.endStopsToCheck = 0; moveBuffer.moveType = 0; - doingArcMove = false; moveBuffer.xAxes = reprap.GetCurrentXAxes(); moveBuffer.yAxes = reprap.GetCurrentYAxes(); moveBuffer.usePressureAdvance = false; - moveBuffer.filePos = (&gb == fileGCode) ? gb.GetFilePosition(fileInput->BytesCached()) : noFilePosition; - moveBuffer.virtualExtruderPosition = virtualExtruderPosition; axesToSenseLength = 0; // Check to see if the move is a 'homing' move that endstops are checked on. @@ -2060,9 +2114,6 @@ bool GCodes::DoStraightMove(GCodeBuffer& gb, StringRef& reply, bool isCoordinate } } - moveBuffer.canPauseAfter = (moveBuffer.endStopsToCheck == 0); - moveBuffer.canPauseBefore = true; - // Check for 'R' parameter to move relative to a restore point int rParam = (moveBuffer.moveType == 0 && gb.Seen('R')) ? gb.GetIValue() : 0; const RestorePoint * const rp = (rParam == 1) ? &pauseRestorePoint : (rParam == 2) ? &toolChangeRestorePoint : nullptr; @@ -2192,11 +2243,12 @@ bool GCodes::DoStraightMove(GCodeBuffer& gb, StringRef& reply, bool isCoordinate } } - segmentsLeft = totalSegments; + doingArcMove = false; + FinaliseMove(gb); return false; } -// Execute an arc move returning true if it was badly-formed +// Execute an arc move, returning true if it was badly-formed // We already have the movement lock and the last move has gone // Currently, we do not process new babystepping when executing an arc move bool GCodes::DoArcMove(GCodeBuffer& gb, bool clockwise) @@ -2256,13 +2308,9 @@ bool GCodes::DoArcMove(GCodeBuffer& gb, bool clockwise) // Set up default move parameters moveBuffer.endStopsToCheck = 0; moveBuffer.moveType = 0; - moveBuffer.canPauseAfter = moveBuffer.canPauseBefore = true; moveBuffer.xAxes = reprap.GetCurrentXAxes(); moveBuffer.yAxes = reprap.GetCurrentYAxes(); - moveBuffer.virtualExtruderPosition = virtualExtruderPosition; - moveBuffer.endStopsToCheck = 0; moveBuffer.isCoordinated = true; - moveBuffer.filePos = (&gb == fileGCode) ? gb.GetFilePosition(fileInput->BytesCached()) : noFilePosition; // Set up the arc centre coordinates and record which axes behave like an X axis. // The I and J parameters are always relative to present position. @@ -2299,14 +2347,45 @@ bool GCodes::DoArcMove(GCodeBuffer& gb, bool clockwise) { arcAngleIncrement = -arcAngleIncrement; } - doingArcMove = true; - segmentsLeft = totalSegments; // must do this last for RTOS + doingArcMove = true; + FinaliseMove(gb); // debugPrintf("Radius %.2f, initial angle %.1f, increment %.1f, segments %u\n", // arcRadius, arcCurrentAngle * RadiansToDegrees, arcAngleIncrement * RadiansToDegrees, segmentsLeft); return false; } +// Adjust the move parameters to account for segmentation and/or part of the move having been done already +void GCodes::FinaliseMove(const GCodeBuffer& gb) +{ + moveBuffer.canPauseAfter = (moveBuffer.endStopsToCheck == 0); + moveBuffer.canPauseBefore = true; + moveBuffer.filePos = (&gb == fileGCode) ? gb.GetFilePosition(fileInput->BytesCached()) : noFilePosition; + moveBuffer.virtualExtruderPosition = virtualExtruderPosition; + + if (totalSegments > 1) + { + for (size_t drive = numTotalAxes; drive < DRIVES; ++drive) + { + moveBuffer.coords[drive] /= totalSegments; // change the extrusion to extrusion per segment + } + + if (moveFractionToSkip != 0.0) + { + const float fseg = floor(totalSegments * moveFractionToSkip); // round down to the start of a move + segmentsLeftToStartAt = totalSegments - (unsigned int)fseg; + firstSegmentFractionToSkip = (moveFractionToSkip * totalSegments) - fseg; + segmentsLeft = totalSegments; // do this last, ready for RTOS + return; + } + } + + segmentsLeftToStartAt = totalSegments; + firstSegmentFractionToSkip = moveFractionToSkip; + + segmentsLeft = totalSegments; // do this last, ready for RTOS +} + // The Move class calls this function to find what to do next. bool GCodes::ReadMove(RawMove& m) { @@ -2320,16 +2399,20 @@ bool GCodes::ReadMove(RawMove& m) if (segmentsLeft == 1) { // If there is just 1 segment left, it doesn't matter if it is an arc move or not, just move to the end position + if (segmentsLeftToStartAt == 1 && firstSegmentFractionToSkip != 0.0) // if this is the segment we are starting at and we need to skip some of it + { + // Reduce the extrusion by the amount to be skipped + for (size_t drive = numTotalAxes; drive < DRIVES; ++drive) + { + m.coords[drive] *= (1.0 - firstSegmentFractionToSkip); + } + } + m.proportionLeft = 0.0; ClearMove(); - m.proportionRemaining = 0; } else { // This move needs to be divided into 2 or more segments - m.canPauseAfter = false; // we can only do a controlled pause pause after the final segment - moveBuffer.canPauseBefore = false; // we can't do a controlled pause before any of the remaining segments - m.proportionRemaining = (uint8_t)min<unsigned int>((((totalSegments - segmentsLeft - 1) * 256) + (totalSegments/2))/totalSegments, 255); - // Do the axes if (doingArcMove) { @@ -2359,20 +2442,23 @@ bool GCodes::ReadMove(RawMove& m) m.coords[drive] = moveBuffer.initialCoords[drive]; } - // Do the extruders - for (size_t drive = numTotalAxes; drive < DRIVES; ++drive) - { - const float extrusionToDo = moveBuffer.coords[drive]/segmentsLeft; - m.coords[drive] = extrusionToDo; - moveBuffer.coords[drive] -= extrusionToDo; - } - - --segmentsLeft; - if ((unsigned int)moveFractionToSkip + (unsigned int)m.proportionRemaining > 256) + if (segmentsLeftToStartAt < segmentsLeft) { // We are resuming a print part way through a move and we printed this segment already + --segmentsLeft; return false; } + if (segmentsLeftToStartAt == segmentsLeft && firstSegmentFractionToSkip != 0.0) // if this is the segment we are starting at and we need to skip some of it + { + // Reduce the extrusion by the amount to be skipped + for (size_t drive = numTotalAxes; drive < DRIVES; ++drive) + { + m.coords[drive] *= (1.0 - firstSegmentFractionToSkip); + } + } + --segmentsLeft; + + m.proportionLeft = (float)segmentsLeft/(float)totalSegments; } return true; @@ -2385,7 +2471,7 @@ void GCodes::ClearMove() moveBuffer.endStopsToCheck = 0; moveBuffer.moveType = 0; moveBuffer.isFirmwareRetraction = false; - moveFractionToSkip = 0; + moveFractionToSkip = 0.0; } // Run a file macro. Prior to calling this, 'state' must be set to the state we want to enter when the macro has been completed. @@ -3059,7 +3145,7 @@ bool GCodes::QueueFileToPrint(const char* fileName, StringRef& reply) fileToPrint.Set(f); fileOffsetToPrint = 0; - moveFractionToStartAt = 0; + moveFractionToStartAt = 0.0; return true; } @@ -4466,12 +4552,59 @@ const char* GCodes::GetMachineModeString() const } } -// Respond to a heater fault. The heater has already been turned off and its status set to 'fault' when this is called. +// Respond to a heater fault. The heater has already been turned off and its status set to 'fault' when this is called from the Heat module. +// The Heat module will generate an appropriate error message, so on need to do that here. void GCodes::HandleHeaterFault(int heater) { - if (IsReallyPrinting()) + if (heaterFaultState == HeaterFaultState::noFault && fileGCode->OriginalMachineState().fileState.IsLive()) { - DoPause(*autoPauseGCode, PauseReason::heaterFault, "Fault on heater %d", heater); + heaterFaultState = HeaterFaultState::pausePending; + heaterFaultTime = millis(); + } +} + +// Check for and respond to a heater fault, returning true if we should exit +void GCodes::CheckHeaterFault() +{ + switch (heaterFaultState) + { + case HeaterFaultState::noFault: + default: + break; + + case HeaterFaultState::pausePending: + if ( IsReallyPrinting() + && autoPauseGCode->IsCompletelyIdle() + && LockMovement(*autoPauseGCode) // need to lock movement before executing the pause macro + ) + { + reprap.GetHeat().SwitchOffAll(false); // turn off all extruder heaters + DoPause(*autoPauseGCode, PauseReason::heaterFault, "Heater fault"); + heaterFaultState = HeaterFaultState::timing; + } + else if (IsPausing() || IsPaused()) + { + heaterFaultState = HeaterFaultState::timing; + } + // no break + + case HeaterFaultState::timing: + if (millis() - heaterFaultTime >= heaterFaultTimeout) + { + StopPrint(false); + reprap.GetHeat().SwitchOffAll(true); + reprap.GetPlatform().MessageF(ErrorMessage, "Shutting down due to un-cleared heater fault after %lu seconds\n", heaterFaultTimeout/1000); + heaterFaultState = HeaterFaultState::stopping; + } + break; + + case HeaterFaultState::stopping: + if (millis() - heaterFaultTime >= 2000) // wait 2 seconds for the message to be picked up by DWC and PanelDue + { + reprap.GetPlatform().SetAtxPower(false); + heaterFaultState = HeaterFaultState::stopped; + } + break; } } diff --git a/src/GCodes/GCodes.h b/src/GCodes/GCodes.h index e44f7756..72bf6bd1 100644 --- a/src/GCodes/GCodes.h +++ b/src/GCodes/GCodes.h @@ -107,6 +107,7 @@ public: float feedRate; // feed rate of this move float virtualExtruderPosition; // the virtual extruder position at the start of this move FilePosition filePos; // offset in the file being printed at the start of reading this move + float proportionLeft; // what proportion of the entire move remains after this segment AxesBitmap xAxes; // axes that X is mapped to AxesBitmap yAxes; // axes that Y is mapped to EndstopChecks endStopsToCheck; // endstops to check @@ -114,7 +115,6 @@ public: IoBits_t ioBits; // I/O bits to set/clear at the start of this move #endif uint8_t moveType; // the S parameter from the G0 or G1 command, 0 for a normal move - uint8_t proportionRemaining; // what proportion of the entire move remains after this segment uint8_t isFirmwareRetraction : 1; // true if this is a firmware retraction/un-retraction move uint8_t usePressureAdvance : 1; // true if we want to us extruder pressure advance, if there is any extrusion @@ -185,7 +185,11 @@ public: #if HAS_VOLTAGE_MONITOR bool LowVoltagePause(); bool LowVoltageResume(); - bool AutoResumeAfterShutdown(); +#endif + +#if HAS_SMART_DRIVERS + bool PauseOnStall(DriversBitmap stalledDrivers); + bool ReHomeOnStall(DriversBitmap stalledDrivers); #endif const char *GetAxisLetters() const { return axisLetters; } // Return a null-terminated string of axis letters indexed by drive @@ -193,7 +197,7 @@ public: private: GCodes(const GCodes&); // private copy constructor to prevent copying - enum class CannedMoveType : uint8_t { none, relative, absolute }; + enum class HeaterFaultState : uint8_t { noFault, pausePending, timing, stopping, stopped }; // Resources that can be locked. // To avoid deadlock, if you need multiple resources then you must lock them in increasing numerical order. @@ -231,6 +235,7 @@ private: bool DoStraightMove(GCodeBuffer& gb, StringRef& reply, bool isCoordinated) __attribute__((hot)); // Execute a straight move returning true if an error was written to 'reply' bool DoArcMove(GCodeBuffer& gb, bool clockwise) // Execute an arc move returning true if it was badly-formed pre(segmentsLeft == 0; resourceOwners[MoveResource] == &gb); + void FinaliseMove(const GCodeBuffer& gb); // Adjust the move parameters to account for segmentation and/or part of the move having been done already GCodeResult DoDwell(GCodeBuffer& gb); // Wait for a bit GCodeResult DoDwellTime(GCodeBuffer& gb, uint32_t dwellMillis); // Really wait for a bit @@ -278,10 +283,15 @@ private: void ListTriggers(StringRef reply, TriggerInputsBitmap mask); // Append a list of trigger inputs to a message void CheckTriggers(); // Check for and execute triggers void CheckFilament(); // Check for and respond to filament errors + void CheckHeaterFault(); // Check for and respond to a heater fault, returning true if we should exit void DoEmergencyStop(); // Execute an emergency stop - void DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg, ...) __attribute__ ((format (printf, 4, 5))); // Pause the print - pre(resourceOwners[movementResource] = &gb); + void DoPause(GCodeBuffer& gb, PauseReason reason, const char *msg) // Pause the print + pre(resourceOwners[movementResource] = &gb); + +#if HAS_VOLTAGE_MONITOR || HAS_SMART_DRIVERS + bool DoEmergencyPause(); // Do an emergency pause following loss of power or a motor stall +#endif void SetMappedFanSpeed(); // Set the speeds of fans mapped for the current tool void SaveFanSpeeds(); // Save the speeds of all fans @@ -338,10 +348,7 @@ private: bool runningConfigFile; // We are running config.g during the startup process bool doingToolChange; // We are running tool change macros - uint8_t moveFractionToStartAt; // how much of the next move was printed before the power failure - uint8_t moveFractionToSkip; - - #if HAS_VOLTAGE_MONITOR +#if HAS_VOLTAGE_MONITOR bool isPowerFailPaused; // true if the print was paused automatically because of a power failure char *powerFailScript; // the commands run when there is a power failure #endif @@ -353,6 +360,12 @@ private: RawMove moveBuffer; // Move details to pass to Move class unsigned int segmentsLeft; // The number of segments left to do in the current move, or 0 if no move available unsigned int totalSegments; // The total number of segments left in the complete move + + unsigned int segmentsLeftToStartAt; + float moveFractionToStartAt; // how much of the next move was printed before the power failure + float moveFractionToSkip; + float firstSegmentFractionToSkip; + float arcCentre[MaxAxes]; float arcRadius; float arcCurrentAngle; @@ -448,6 +461,11 @@ private: float spindleMaxRpm; float laserMaxPower; + // Heater fault handler + HeaterFaultState heaterFaultState; // whether there is a heater fault and what we have done about it so far + uint32_t heaterFaultTime; // when the heater fault occurred + uint32_t heaterFaultTimeout; // how long we wait for the user to fix it before turning everything off + // Misc uint32_t longWait; // Timer for things that happen occasionally (seconds) uint32_t lastWarningMillis; // When we last sent a warning message for things that can happen very often @@ -459,7 +477,9 @@ private: bool displayDeltaNotHomedWarning; // True if we need to display a 'attempt to move before homing on a delta printer' message char filamentToLoad[FilamentNameLength]; // Name of the filament being loaded + // Standard macro filenames static constexpr const char* BED_EQUATION_G = "bed.g"; + static constexpr const char* PAUSE_G = "pause.g"; static constexpr const char* RESUME_G = "resume.g"; static constexpr const char* CANCEL_G = "cancel.g"; static constexpr const char* STOP_G = "stop.g"; @@ -467,14 +487,14 @@ private: static constexpr const char* CONFIG_OVERRIDE_G = "config-override.g"; static constexpr const char* DEPLOYPROBE_G = "deployprobe.g"; static constexpr const char* RETRACTPROBE_G = "retractprobe.g"; - static constexpr const char* PAUSE_G = "pause.g"; - static constexpr const char* HOME_ALL_G = "homeall.g"; - static constexpr const char* HOME_DELTA_G = "homedelta.g"; static constexpr const char* DefaultHeightMapFile = "heightmap.csv"; static constexpr const char* LOAD_FILAMENT_G = "load.g"; static constexpr const char* UNLOAD_FILAMENT_G = "unload.g"; static constexpr const char* RESUME_AFTER_POWER_FAIL_G = "resurrect.g"; static constexpr const char* RESUME_PROLOGUE_G = "resurrect-prologue.g"; +#if HAS_SMART_DRIVERS + static constexpr const char* REHOME_G = "rehome.g"; +#endif static constexpr const float MinServoPulseWidth = 544.0, MaxServoPulseWidth = 2400.0; static const uint16_t ServoRefreshFrequency = 50; diff --git a/src/GCodes/GCodes2.cpp b/src/GCodes/GCodes2.cpp index 69626468..33dfdc64 100644 --- a/src/GCodes/GCodes2.cpp +++ b/src/GCodes/GCodes2.cpp @@ -19,6 +19,7 @@ #include "RepRap.h" #include "Tools/Tool.h" #include "FilamentSensors/FilamentSensor.h" +#include "Libraries/General/IP4String.h" #include "Version.h" #if SUPPORT_IOBITS @@ -88,13 +89,13 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) switch (code) { - case 0: // There are no rapid moves... + case 0: // Rapid move case 1: // Ordinary move - if (!LockMovement(gb)) + if (segmentsLeft != 0) // do this check first to avoid locking movement unnecessarily { return false; } - if (segmentsLeft != 0) + if (!LockMovement(gb)) { return false; } @@ -107,11 +108,11 @@ bool GCodes::HandleGcode(GCodeBuffer& gb, StringRef& reply) case 2: // Clockwise arc case 3: // Anti clockwise arc // We only support X and Y axes in these (and optionally Z for corkscrew moves), but you can map them to other axes in the tool definitions - if (!LockMovement(gb)) + if (segmentsLeft != 0) // do this check first to avoid locking movement unnecessarily { return false; } - if (segmentsLeft != 0) + if (!LockMovement(gb)) { return false; } @@ -691,7 +692,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) fileOffsetToPrint = (FilePosition)gb.GetUIValue(); if (gb.Seen('P')) { - moveFractionToStartAt = (uint8_t)constrain<unsigned long>(lrintf(gb.GetFValue()), 0, 255); + moveFractionToStartAt = constrain<float>(gb.GetFValue(), 0.0, 1.0); } } break; @@ -891,32 +892,54 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) if (gb.Seen('P')) { const LogicalPin logicalPin = gb.GetIValue(); - Pin pin; - bool invert; - if (platform.GetFirmwarePin(logicalPin, PinAccess::pwm, pin, invert)) + if (gb.Seen('S')) { - if (gb.Seen('S')) + float val = gb.GetFValue(); + if (val > 1.0) + { + val /= 255.0; + } + val = constrain<float>(val, 0.0, 1.0); + + // The SX1509B I/O expander chip doesn't seem to work if you set PWM mode and then set digital output mode. + // This cases a problem if M42 is used to write to some pins and then M670 is used to set up the G1 P parameter port mapping. + // The first part of the fix for this is to not select PWM mode if we don't need to. + bool usePwm; + uint16_t freq; + if (gb.Seen('F')) + { + freq = constrain<int32_t>(gb.GetIValue(), 1, 65536); + usePwm = true; + } + else + { + freq = DefaultPinWritePwmFreq; + usePwm = (val != 0.0 && val != 1.0); + } + + Pin pin; + bool invert; + if (platform.GetFirmwarePin(logicalPin, (usePwm) ? PinAccess::pwm : PinAccess::write, pin, invert)) { - float val = gb.GetFValue(); - if (val > 1.0) - { - val /= 255.0; - } - val = constrain<float>(val, 0.0, 1.0); if (invert) { val = 1.0 - val; } - const uint16_t freq = (gb.Seen('F')) ? (uint16_t)constrain<int32_t>(gb.GetIValue(), 1, 65536) : DefaultPinWritePwmFreq; - IoPort::WriteAnalog(pin, val, freq); + if (usePwm) + { + IoPort::WriteAnalog(pin, val, freq); + } + else + { + IoPort::WriteDigital(pin, val == 1.0); + } + } + else + { + reply.printf("Logical pin %d is not available for writing", logicalPin); + result = GCodeResult::error; } - // Ignore the command if no S parameter provided - } - else - { - reply.printf("Logical pin %d is not available for writing", logicalPin); - result = GCodeResult::error; } } break; @@ -2014,7 +2037,8 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) float gain = model.GetGain(), tc = model.GetTimeConstant(), td = model.GetDeadTime(), - maxPwm = model.GetMaxPwm(); + maxPwm = model.GetMaxPwm(), + voltage = model.GetVoltage(); int32_t dontUsePid = model.UsePid() ? 0 : 1; gb.TryGetFValue('A', gain, seen); @@ -2022,10 +2046,11 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) gb.TryGetFValue('D', td, seen); gb.TryGetIValue('B', dontUsePid, seen); gb.TryGetFValue('S', maxPwm, seen); + gb.TryGetFValue('V', voltage, seen); if (seen) { - if (!reprap.GetHeat().SetHeaterModel(heater, gain, tc, td, maxPwm, dontUsePid == 0)) + if (!reprap.GetHeat().SetHeaterModel(heater, gain, tc, td, maxPwm, voltage, dontUsePid == 0)) { reply.copy("Error: bad model parameters"); } @@ -2036,11 +2061,11 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) } else { - const char* mode = (!model.UsePid()) ? "bang-bang" - : (model.ArePidParametersOverridden()) ? "custom PID" - : "PID"; - reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, mode: %s", - heater, (double)model.GetGain(), (double)model.GetTimeConstant(), (double)model.GetDeadTime(), (double)model.GetMaxPwm(), mode); + const char* const mode = (!model.UsePid()) ? "bang-bang" + : (model.ArePidParametersOverridden()) ? "custom PID" + : "PID"; + reply.printf("Heater %u model: gain %.1f, time constant %.1f, dead time %.1f, max PWM %.2f, calibration voltage %.1f, mode: %s", + heater, (double)model.GetGain(), (double)model.GetTimeConstant(), (double)model.GetDeadTime(), (double)model.GetMaxPwm(), (double)model.GetVoltage(), mode); if (model.UsePid()) { // When reporting the PID parameters, we scale them by 255 for compatibility with older firmware and other firmware @@ -2672,6 +2697,15 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) result = GCodeResult::error; } } + else + { + // Clear all heater faults + for (int heater = 0; heater < (int)Heaters; ++heater) + { + reprap.ClearTemperatureFault(heater); + } + } + heaterFaultState = HeaterFaultState::noFault; break; case 563: // Define tool @@ -3518,47 +3552,38 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) else { // List remembered networks - const size_t declaredBufferLength = (MaxRememberedNetworks + 1) * (SsidLength + 1) + 1; // enough for all the remembered SSIDs with newline terminator, plus an extra null - uint32_t buffer[NumDwords(declaredBufferLength + 1)]; - const int32_t rslt = reprap.GetNetwork().SendCommand(NetworkCommand::networkListSsids, 0, 0, nullptr, 0, buffer, declaredBufferLength); + const size_t declaredBufferLength = (MaxRememberedNetworks + 1) * ReducedWirelessConfigurationDataSize; // enough for all the remembered SSID data + uint32_t buffer[NumDwords(declaredBufferLength)]; + const int32_t rslt = reprap.GetNetwork().SendCommand(NetworkCommand::networkRetrieveSsidData, 0, 0, nullptr, 0, buffer, declaredBufferLength); if (rslt >= 0) { - char* const cbuf = reinterpret_cast<char *>(buffer); - cbuf[declaredBufferLength] = 0; // ensure null terminated - - // DuetWiFiServer 1.19beta7 and later include the SSID used in access point mode at the start - char *bufp = strchr(cbuf, '\n'); - if (bufp == nullptr) + OutputBuffer *response = nullptr; + size_t offset = ReducedWirelessConfigurationDataSize; // skip own SSID details + while (offset + ReducedWirelessConfigurationDataSize <= (size_t)rslt) { - bufp = cbuf; // must be an old version of DuetWiFiServer - } - else - { - ++bufp; // slip the first entry - } - - // If there is a trailing newline, remove it - { - const size_t len = strlen(bufp); - if (len != 0 && bufp[len - 1] == '\n') + WirelessConfigurationData* const wp = reinterpret_cast<WirelessConfigurationData *>(reinterpret_cast<char*>(buffer) + offset); + if (wp->ssid[0] != 0) { - bufp[len - 1] = 0; + if (response == nullptr) + { + if (!OutputBuffer::Allocate(response)) + { + return false; // try again later + } + response->copy("Remembered networks:"); + } + wp->ssid[ARRAY_UPB(wp->ssid)] = 0; + response->catf("\n%s IP=%s GW=%s NM=%s", wp->ssid, IP4String(wp->ip).c_str(), IP4String(wp->gateway).c_str(), IP4String(wp->netmask).c_str()); } + offset += ReducedWirelessConfigurationDataSize; } - if (strlen(bufp) == 0) + if (response == nullptr) { reply.copy("No remembered networks"); } else { - OutputBuffer *response; - if (!OutputBuffer::Allocate(response)) - { - return false; // try again later - } - response->copy("Remembered networks:\n"); - response->cat(bufp); HandleReply(gb, false, response); return true; } @@ -3633,10 +3658,10 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) ok = gb.GetIPAddress(config.ip); config.channel = (gb.Seen('C')) ? gb.GetIValue() : 0; } - } - else - { - ok = false; + else + { + ok = false; + } } } } @@ -3657,23 +3682,24 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) } else { - const size_t declaredBufferLength = (MaxRememberedNetworks + 1) * (SsidLength + 1) + 1; // enough for all the remembered SSIDs with null terminator, plus an extra null - uint32_t buffer[NumDwords(declaredBufferLength + 1)]; - const int32_t rslt = reprap.GetNetwork().SendCommand(NetworkCommand::networkListSsids, 0, 0, nullptr, 0, buffer, declaredBufferLength); + uint32_t buffer[NumDwords(ReducedWirelessConfigurationDataSize)]; + const int32_t rslt = reprap.GetNetwork().SendCommand(NetworkCommand::networkRetrieveSsidData, 0, 0, nullptr, 0, buffer, ReducedWirelessConfigurationDataSize); if (rslt >= 0) { - char* const cbuf = reinterpret_cast<char *>(buffer); - cbuf[declaredBufferLength] = 0; // ensure null terminated - char *p = strchr(cbuf, '\n'); - if (p != nullptr) + WirelessConfigurationData* const wp = reinterpret_cast<WirelessConfigurationData *>(buffer); + if (wp->ssid[0] == 0) + { + reply.copy("Own SSID not configured"); + } + else { - *p = 0; + wp->ssid[ARRAY_UPB(wp->ssid)] = 0; + reply.printf("Own SSID: %s IP=%s GW=%s NM=%s", wp->ssid, IP4String(wp->ip).c_str(), IP4String(wp->gateway).c_str(), IP4String(wp->netmask).c_str()); } - reply.printf("Own SSID: %s", (cbuf[0] == 0) ? "not configured" : cbuf); } else { - reply.copy("Failed to remove SSID from remembered list"); + reply.copy("Failed to retrieve own SSID data"); result = GCodeResult::error; } } @@ -4319,7 +4345,7 @@ bool GCodes::HandleMcode(GCodeBuffer& gb, StringRef& reply) { return false; } - reprap.GetHeat().SwitchOffAll(); // turn all heaters off because the main loop may get suspended + reprap.GetHeat().SwitchOffAll(true); // turn all heaters off because the main loop may get suspended DisableDrives(); // all motors off if (firmwareUpdateModuleMap == 0) // have we worked out which modules to update? @@ -4453,17 +4479,27 @@ bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply) return true; // when running M502 we don't execute T commands } + bool seen = false; + int toolNum; if (gb.HasCommandNumber()) { + seen = true; + toolNum = gb.GetCommandNumber(); + toolNum += gb.GetToolNumberAdjust(); + } + else if (gb.Seen('R') && gb.GetIValue() == 1) + { + seen = true; + toolNum = pauseRestorePoint.toolNumber; + } + + if (seen) + { if (!LockMovementAndWaitForStandstill(gb)) { return false; } - // See if the tool can be changed - int toolNum = gb.GetCommandNumber(); - toolNum += gb.GetToolNumberAdjust(); - const Tool * const oldTool = reprap.GetCurrentTool(); // If old and new are the same we no longer follow the sequence. User can deselect and then reselect the tool if he wants the macros run. if (oldTool == nullptr || oldTool->Number() != toolNum) @@ -4477,7 +4513,7 @@ bool GCodes::HandleTcode(GCodeBuffer& gb, StringRef& reply) } else { - // Even though the tool is selected, we may have turned it off e.g. when upgrading the WiFi firmware. + // Even though the tool is selected, we may have turned it off e.g. when upgrading the WiFi firmware or following a heater fault that has been cleared. // So make sure the tool heaters are on. reprap.SelectTool(toolNum, simulationMode != 0); } diff --git a/src/GCodes/RestorePoint.cpp b/src/GCodes/RestorePoint.cpp index 977730de..b75fe8a4 100644 --- a/src/GCodes/RestorePoint.cpp +++ b/src/GCodes/RestorePoint.cpp @@ -18,13 +18,17 @@ void RestorePoint::Init() { moveCoords[i] = 0.0; } + feedRate = DefaultFeedrate * SecondsToMinutes; virtualExtruderPosition = 0.0; - proportionDone = 0.0; filePos = noFilePosition; + proportionDone = 0.0; + #if SUPPORT_IOBITS ioBits = 0; #endif + + toolNumber = -1; } // End diff --git a/src/GCodes/RestorePoint.h b/src/GCodes/RestorePoint.h index 129d6100..70a93d3b 100644 --- a/src/GCodes/RestorePoint.h +++ b/src/GCodes/RestorePoint.h @@ -19,11 +19,13 @@ struct RestorePoint float moveCoords[MaxAxes]; // The axis locations when we paused float feedRate; // The feed rate for the current move float virtualExtruderPosition; // The virtual extruder position at the start of this move - float proportionDone; // How much of this move we have already done (zero unless we interrupts a move) + float proportionDone; // How much of this move we have already done (zero unless we interrupted a move) FilePosition filePos; // The file position that this move was read from + #if SUPPORT_IOBITS IoBits_t ioBits; // The output port bits setting for this move #endif + int toolNumber; // The tool number that was active RestorePoint(); void Init(); diff --git a/src/Heating/FOPDT.cpp b/src/Heating/FOPDT.cpp index 0625b28e..6acc67ab 100644 --- a/src/Heating/FOPDT.cpp +++ b/src/Heating/FOPDT.cpp @@ -14,13 +14,13 @@ extern StringRef scratchString; // Heater 6 on the Duet 0.8.5 is disabled by default at startup so that we can use fan 2. // Set up sensible defaults here in case the user enables the heater without specifying values for all the parameters. FopDt::FopDt() - : gain(DefaultHotEndHeaterGain), timeConstant(DefaultHotEndHeaterTimeConstant), deadTime(DefaultHotEndHeaterDeadTime), maxPwm(1.0), + : gain(DefaultHotEndHeaterGain), timeConstant(DefaultHotEndHeaterTimeConstant), deadTime(DefaultHotEndHeaterDeadTime), maxPwm(1.0), standardVoltage(0.0), enabled(false), usePid(true), pidParametersOverridden(false) { } // Check the model parameters are sensible, if they are then save them and return true. -bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, bool pUsePid) +bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, float voltage, bool pUsePid) { if (pg == -1.0 && ptc == -1.0 && pdt == -1.0) { @@ -37,6 +37,7 @@ bool FopDt::SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float t timeConstant = ptc; deadTime = pdt; maxPwm = pMaxPwm; + standardVoltage = voltage; usePid = pUsePid; enabled = true; CalcPidConstants(); @@ -69,7 +70,8 @@ void FopDt::SetM301PidParameters(const M301PidParameters& pp) // Write the model parameters to file returning true if no error bool FopDt::WriteParameters(FileStore *f, size_t heater) const { - scratchString.printf("M307 H%u A%.1f C%.1f D%.1f S%.2f B%d\n", heater, (double)gain, (double)timeConstant, (double)deadTime, (double)maxPwm, (usePid) ? 0 : 1); + scratchString.printf("M307 H%u A%.1f C%.1f D%.1f S%.2f V%.1f B%d\n", + heater, (double)gain, (double)timeConstant, (double)deadTime, (double)maxPwm, (double)standardVoltage, (usePid) ? 0 : 1); bool ok = f->Write(scratchString.Pointer()); if (ok && pidParametersOverridden) { diff --git a/src/Heating/FOPDT.h b/src/Heating/FOPDT.h index 36fd6355..2cf2c62c 100644 --- a/src/Heating/FOPDT.h +++ b/src/Heating/FOPDT.h @@ -35,12 +35,13 @@ class FopDt public: FopDt(); - bool SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, bool pUsePid); + bool SetParameters(float pg, float ptc, float pdt, float pMaxPwm, float temperatureLimit, float voltage, bool pUsePid); float GetGain() const { return gain; } float GetTimeConstant() const { return timeConstant; } float GetDeadTime() const { return deadTime; } float GetMaxPwm() const { return maxPwm; } + float GetVoltage() const { return standardVoltage; } bool UsePid() const { return usePid; } bool IsEnabled() const { return enabled; } bool ArePidParametersOverridden() const { return pidParametersOverridden; } @@ -61,6 +62,7 @@ private: float timeConstant; float deadTime; float maxPwm; + float standardVoltage; // power voltage reading at which tuning was done, or 0 if unknown bool enabled; bool usePid; bool pidParametersOverridden; diff --git a/src/Heating/Heat.cpp b/src/Heating/Heat.cpp index e1326c76..05dd890b 100644 --- a/src/Heating/Heat.cpp +++ b/src/Heating/Heat.cpp @@ -45,11 +45,11 @@ void Heat::ResetHeaterModels() { if ((int)heater == DefaultBedHeater || (int)heater == DefaultChamberHeater) { - pids[heater]->SetModel(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, false); + pids[heater]->SetModel(DefaultBedHeaterGain, DefaultBedHeaterTimeConstant, DefaultBedHeaterDeadTime, 1.0, 0.0, false); } else { - pids[heater]->SetModel(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, 1.0, true); + pids[heater]->SetModel(DefaultHotEndHeaterGain, DefaultHotEndHeaterTimeConstant, DefaultHotEndHeaterDeadTime, 1.0, 0.0, true); } } } @@ -284,11 +284,14 @@ void Heat::SwitchOff(int8_t heater) } } -void Heat::SwitchOffAll() +void Heat::SwitchOffAll(bool includingChamberAndBed) { - for (PID *p : pids) + for (int heater = 0; heater < (int)Heaters; ++heater) { - p->SwitchOff(); + if (includingChamberAndBed || (heater != bedHeater && heater != chamberHeater)) + { + pids[heater]->SwitchOff(); + } } } @@ -319,7 +322,7 @@ uint32_t Heat::GetLastSampleTime(size_t heater) const return pids[heater]->GetLastSampleTime(); } -bool Heat::UseSlowPwm(int8_t heater) const +bool Heat::IsBedOrChamberHeater(int8_t heater) const { return heater == bedHeater || heater == chamberHeater; } diff --git a/src/Heating/Heat.h b/src/Heating/Heat.h index cdfc9c99..35b21181 100644 --- a/src/Heating/Heat.h +++ b/src/Heating/Heat.h @@ -71,7 +71,7 @@ public: float GetTargetTemperature(int8_t heater) const; // Get the target temperature HeaterStatus GetStatus(int8_t heater) const; // Get the off/standby/active status void SwitchOff(int8_t heater); // Turn off a specific heater - void SwitchOffAll(); // Turn all heaters off + void SwitchOffAll(bool includingChamberAndBed); // Turn all heaters off void ResetFault(int8_t heater); // Reset a heater fault - only call this if you know what you are doing bool AllHeatersAtSetTemperatures(bool includingBed) const; // Is everything at temperature within tolerance? bool HeaterAtSetTemperature(int8_t heater, bool waitWhenCooling) const; // Is a specific heater at temperature within tolerance? @@ -80,7 +80,7 @@ public: float GetAveragePWM(size_t heater) const // Return the running average PWM to the heater as a fraction in [0, 1]. pre(heater < Heaters); - bool UseSlowPwm(int8_t heater) const; // Queried by the Platform class + bool IsBedOrChamberHeater(int8_t heater) const; // Queried by the Platform class uint32_t GetLastSampleTime(size_t heater) const pre(heater < Heaters); @@ -96,7 +96,7 @@ public: const FopDt& GetHeaterModel(size_t heater) const // Get the process model for the specified heater pre(heater < Heaters); - bool SetHeaterModel(size_t heater, float gain, float tc, float td, float maxPwm, bool usePid) // Set the heater process model + bool SetHeaterModel(size_t heater, float gain, float tc, float td, float maxPwm, float voltage, bool usePid) // Set the heater process model pre(heater < Heaters); void GetHeaterProtection(size_t heater, float& maxTempExcursion, float& maxFaultTime) const @@ -187,9 +187,9 @@ inline const FopDt& Heat::GetHeaterModel(size_t heater) const } // Set the heater process model -inline bool Heat::SetHeaterModel(size_t heater, float gain, float tc, float td, float maxPwm, bool usePid) +inline bool Heat::SetHeaterModel(size_t heater, float gain, float tc, float td, float maxPwm, float voltage, bool usePid) { - return pids[heater]->SetModel(gain, tc, td, maxPwm, usePid); + return pids[heater]->SetModel(gain, tc, td, maxPwm, voltage, usePid); } // Is the heater enabled? diff --git a/src/Heating/Pid.cpp b/src/Heating/Pid.cpp index 236981bb..aa9db611 100644 --- a/src/Heating/Pid.cpp +++ b/src/Heating/Pid.cpp @@ -31,6 +31,11 @@ float PID::tuningPeakTemperature; // the peak temperature reached, averaged ov uint32_t PID::tuningHeatingTime; // how long we had the heating on for uint32_t PID::tuningPeakDelay; // how many milliseconds the temperature continues to rise after turning the heater off +#if HAS_VOLTAGE_MONITOR +unsigned int voltageSamplesTaken; // how many readings we accumulated +float tuningVoltageAccumulator; // sum of the voltage readings we take during the heating phase +#endif + // Member functions and constructors PID::PID(Platform& p, int8_t h) : platform(p), heater(h), mode(HeaterMode::off) @@ -47,7 +52,7 @@ void PID::Init(float pGain, float pTc, float pTd, float tempLimit, bool usePid) temperatureLimit = tempLimit; maxTempExcursion = DefaultMaxTempExcursion; maxHeatingFaultTime = DefaultMaxHeatingFaultTime; - model.SetParameters(pGain, pTc, pTd, 1.0, tempLimit, usePid); + model.SetParameters(pGain, pTc, pTd, 1.0, tempLimit, 0.0, usePid); Reset(); if (model.IsEnabled()) @@ -80,10 +85,10 @@ void PID::Reset() } // Set the process model -bool PID::SetModel(float gain, float tc, float td, float maxPwm, bool usePid) +bool PID::SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid) { const float temperatureLimit = reprap.GetHeat().GetTemperatureLimit(heater); - const bool rslt = model.SetParameters(gain, tc, td, maxPwm, temperatureLimit, usePid); + const bool rslt = model.SetParameters(gain, tc, td, maxPwm, temperatureLimit, voltage, usePid); if (rslt) { #if defined(DUET_06_085) @@ -216,6 +221,7 @@ void PID::Spin() tuningTempReadings = nullptr; } mode = HeaterMode::fault; + reprap.GetGCodes().HandleHeaterFault(heater); platform.MessageF(ErrorMessage, "Temperature reading fault on heater %d: %s\n", heater, TemperatureErrorString(err)); reprap.FlagTemperatureFault(heater); } @@ -356,15 +362,33 @@ void PID::Spin() } else { +#if 1 // try normal PWM instead, because it looks like the modified PWM may be causing undershoot on initial heating + const float errorToUse = error; +#else // In the following we use a modified PID when the temperature is a long way off target. // During initial heating or cooling, the D term represents expected overshoot, which we don't want to add to the I accumulator. // When we are in load mode, the I term is much larger and the D term doesn't represent overshoot, so use normal PID. const float errorToUse = (inLoadMode || model.ArePidParametersOverridden()) ? error : errorMinusDterm; +#endif iAccumulator = constrain<float> (iAccumulator + (errorToUse * params.kP * params.recipTi * platform.HeatSampleInterval() * MillisToSeconds), 0.0, model.GetMaxPwm()); lastPwm = constrain<float>(pPlusD + iAccumulator, 0.0, model.GetMaxPwm()); } +#if HAS_VOLTAGE_MONITOR + // Scale the PID based on the current voltage vs. the calibration voltage + if (lastPwm < 1.0 && model.GetVoltage() >= 10.0) // if heater is not fully on and we know the voltage we tuned the heater at + { + if (!reprap.GetHeat().IsBedOrChamberHeater(heater)) + { + const float currentVoltage = platform.GetCurrentPowerVoltage(); + if (currentVoltage >= 10.0) // if we have a sensible reading + { + lastPwm = min<float>(lastPwm * fsquare(model.GetVoltage()/currentVoltage), 1.0); // adjust the PWM by the square of the voltage ratio + } + } + } +#endif } else { @@ -447,9 +471,12 @@ void PID::Standby() void PID::ResetFault() { - mode = HeaterMode::off; - SwitchOff(); badTemperatureCount = 0; + if (mode == HeaterMode::fault) + { + mode = HeaterMode::off; + SwitchOff(); + } } float PID::GetAveragePWM() const @@ -610,6 +637,10 @@ void PID::DoTuningStep() { // Starting temperature is stable, so move on tuningReadingsTaken = 1; +#if HAS_VOLTAGE_MONITOR + tuningVoltageAccumulator = 0.0; + voltageSamplesTaken = 0; +#endif tuningTempReadings[0] = tuningStartTemp = temperature; timeSetHeating = tuningPhaseStartTime = millis(); lastPwm = tuningPwm; // turn on heater at specified power @@ -629,7 +660,7 @@ void PID::DoTuningStep() case HeaterMode::tuning1: // Heating up { - const bool isBedOrChamberHeater = (heater == reprap.GetHeat().GetBedHeater() || heater == reprap.GetHeat().GetChamberHeater()); + const bool isBedOrChamberHeater = reprap.GetHeat().IsBedOrChamberHeater(heater); const uint32_t heatingTime = millis() - tuningPhaseStartTime; const float extraTimeAllowed = (isBedOrChamberHeater) ? 60.0 : 30.0; if (heatingTime > (uint32_t)((model.GetDeadTime() + extraTimeAllowed) * SecondsToMillis) && (temperature - tuningStartTemp) < 3.0) @@ -645,6 +676,10 @@ void PID::DoTuningStep() break; } +#if HAS_VOLTAGE_MONITOR + tuningVoltageAccumulator += platform.GetCurrentPowerVoltage(); + ++voltageSamplesTaken; +#endif if (temperature >= tuningTargetTemp) // if reached target { tuningHeatingTime = heatingTime; @@ -828,7 +863,13 @@ void PID::CalculateModel() //const float td = (float)(tuningPeakDelay + 500) * 0.00065; // take the dead time as 65% of the delay to peak rounded up to a half second const float td = tc * logf((gain + tuningStartTemp - tuningHeaterOffTemp)/(gain + tuningStartTemp - tuningPeakTemperature)) * 1.3; - tuned = SetModel(gain, tc, td, tuningPwm, true); + tuned = SetModel(gain, tc, td, tuningPwm, +#if HAS_VOLTAGE_MONITOR + tuningVoltageAccumulator/voltageSamplesTaken, +#else + 0.0, +#endif + true); if (tuned) { platform.MessageF(LoggedGenericMessage, diff --git a/src/Heating/Pid.h b/src/Heating/Pid.h index 1ca9b5d3..b436060c 100644 --- a/src/Heating/Pid.h +++ b/src/Heating/Pid.h @@ -66,7 +66,7 @@ public: const FopDt& GetModel() const // Get the process model { return model; } - bool SetModel(float gain, float tc, float td, float maxPwm, bool usePid); // Set the process model + bool SetModel(float gain, float tc, float td, float maxPwm, float voltage, bool usePid); // Set the process model bool IsHeaterEnabled() const // Is this heater enabled? { return model.IsEnabled(); } diff --git a/src/Libraries/General/StringRef.h b/src/Libraries/General/StringRef.h index 4bfe4dba..1edcc2b1 100644 --- a/src/Libraries/General/StringRef.h +++ b/src/Libraries/General/StringRef.h @@ -60,11 +60,25 @@ public: const char *Pointer() const { return storage; } char& operator[](size_t index) { return storage[index]; } char operator[](size_t index) const { return storage[index]; } + size_t MaxLength() const { return Len; } - void Clear() const { storage[0] = 0; } + void Clear() { storage[0] = 0; } + size_t cat(char c); private: char storage[Len + 1]; }; +// Append a character if it will fit and return the new length +template<size_t Len> size_t String<Len>::cat(char c) +{ + size_t length = strlen(); + if (length + 1 < Len) + { + storage[length++] = c; + storage[length] = 0; + } + return length; +} + #endif /* STRINGREF_H_ */ diff --git a/src/Logger.cpp b/src/Logger.cpp index 7d19237a..e40f9856 100644 --- a/src/Logger.cpp +++ b/src/Logger.cpp @@ -111,7 +111,7 @@ void Logger::InternalLogMessage(time_t time, const char *message) } // This is called regularly by Platform to give the logger an opportunity to flush the file buffer -void Logger::Flush() +void Logger::Flush(bool forced) { if (logFile.IsLive() && dirty && !inLogger) { @@ -122,7 +122,7 @@ void Logger::Flush() // 2. If it hasn't been flushed for LogFlushInterval milliseconds. const FilePosition currentPos = logFile.GetPosition(); const uint32_t now = millis(); - if (now - lastFlushTime >= LogFlushInterval || currentPos/512 != lastFlushFileSize/512) + if (forced || now - lastFlushTime >= LogFlushInterval || currentPos/512 != lastFlushFileSize/512) { Lock loggerLock(inLogger); logFile.Flush(); diff --git a/src/Logger.h b/src/Logger.h index 04397efe..d0cbdab8 100644 --- a/src/Logger.h +++ b/src/Logger.h @@ -22,7 +22,7 @@ public: void Stop(time_t time); void LogMessage(time_t time, const char *message); void LogMessage(time_t time, OutputBuffer *buf); - void Flush(); + void Flush(bool forced); bool IsActive() const { return logFile.IsLive(); } private: diff --git a/src/Movement/DDA.cpp b/src/Movement/DDA.cpp index 090afbbf..037f5af1 100644 --- a/src/Movement/DDA.cpp +++ b/src/Movement/DDA.cpp @@ -348,7 +348,8 @@ bool DDA::Init(GCodes::RawMove &nextMove, bool doMotorMapping) isPrintingMove = xyMoving && extruding; usePressureAdvance = nextMove.usePressureAdvance; virtualExtruderPosition = nextMove.virtualExtruderPosition; - proportionRemaining = nextMove.proportionRemaining; + proportionLeft = nextMove.proportionLeft; + hadLookaheadUnderrun = false; isLeadscrewAdjustmentMove = false; goingSlow = false; @@ -1529,18 +1530,18 @@ void DDA::MoveAborted() state = completed; } -// Return the approximate (to within 1%) proportion of extrusion for the complete multi-segment move that remains to be done. +// Return the proportion of extrusion for the complete multi-segment move that has already been done. // The move was either not started or was aborted. -float DDA::GetProportionDone() const +float DDA::GetProportionDone(bool moveWasAborted) const { // Get the proportion of extrusion already done at the start of this segment - unsigned int proportionDone = (filePos != noFilePosition && filePos == prev->filePos) - ? 256 - prev->proportionRemaining - : 0; - if (state == completed) + float proportionDone = (filePos != noFilePosition && filePos == prev->filePos) + ? 1.0 - prev->proportionLeft + : 0.0; + if (moveWasAborted) { // The move was aborted, so subtract how much was done - const unsigned int proportionDoneAtEnd = 256 - proportionRemaining; + const float proportionDoneAtEnd = 1.0 - proportionLeft; if (proportionDoneAtEnd > proportionDone) { int32_t taken = 0, left = 0; @@ -1554,13 +1555,13 @@ float DDA::GetProportionDone() const } } const int32_t total = taken + left; - if (total > 0) // if the move has net extrusion + if (total > 0) // if the move has net extrusion { proportionDone += (((proportionDoneAtEnd - proportionDone) * taken) + (total/2)) / total; } } } - return (float)proportionDone/256.0; + return proportionDone; } // Reduce the speed of this move to the indicated speed. diff --git a/src/Movement/DDA.h b/src/Movement/DDA.h index c45c453f..ed974d15 100644 --- a/src/Movement/DDA.h +++ b/src/Movement/DDA.h @@ -74,7 +74,8 @@ public: void LimitSpeedAndAcceleration(float maxSpeed, float maxAcceleration); // Limit the speed an acceleration of this move int32_t GetStepsTaken(size_t drive) const; - float GetProportionDone() const; // Return the proportion of extrusion for the complete multi-segment move already done + + float GetProportionDone(bool moveWasAborted) const; // Return the proportion of extrusion for the complete multi-segment move already done void MoveAborted(); @@ -146,8 +147,7 @@ private: DDA *prev; // The previous one in the ring volatile DDAState state; // What state this DDA is in - uint8_t proportionRemaining; // What proportion of the extrusion in the G1 or G0 move of which this is a part remains to be done after this segment is complete - // - we use a uint8_t instead of a float to save space because it only affects the extrusion amount, so ~0.4% error is acceptable + union { struct @@ -197,6 +197,8 @@ private: uint32_t clocksNeeded; // in clocks uint32_t moveStartTime; // clock count at which the move was started + float proportionLeft; // what proportion of the extrusion in the G1 or G0 move of which this is a part remains to be done after this segment is complete + #if SUPPORT_IOBITS IoBits_t ioBits; // port state required during this move #endif diff --git a/src/Movement/Kinematics/CoreXYUKinematics.cpp b/src/Movement/Kinematics/CoreXYUKinematics.cpp index 80ab8f84..060fa5dc 100644 --- a/src/Movement/Kinematics/CoreXYUKinematics.cpp +++ b/src/Movement/Kinematics/CoreXYUKinematics.cpp @@ -9,10 +9,6 @@ #include "GCodes/GCodes.h" #include "Movement/DDA.h" -const size_t CoreXYU_AXES = 5; -const size_t U_AXIS = 3; // X2 -const size_t V_AXIS = 4; // Y2 - CoreXYUKinematics::CoreXYUKinematics() : CoreBaseKinematics(KinematicsType::coreXYU) { } diff --git a/src/Movement/Move.cpp b/src/Movement/Move.cpp index 4d0248df..fc13803e 100644 --- a/src/Movement/Move.cpp +++ b/src/Movement/Move.cpp @@ -397,7 +397,9 @@ bool Move::PausePrint(RestorePoint& rp) // So on return we need to signal one of the following to GCodes: // 1. We have skipped some moves in the queue. Pass back the file address of the first move we have skipped, the feed rate at the start of that move // and the iobits at the start of that move, and return true. - // 2. All moves in the queue need to be executed. Also any move held by GCodes needs to be completed it is it not the first segment. Return false. + // 2. All moves in the queue need to be executed. Also any move held by GCodes needs to be completed it is it not the first segment. + // Update the restore point with the coordinates and iobits as at the end of the previous move and return false. + // The extruder position, file position and feed rate are not filled in. // // In general, we can pause after a move if it is the last segment and its end speed is slow enough. // We can pause before a move if it is the first segment in that move. @@ -432,12 +434,7 @@ bool Move::PausePrint(RestorePoint& rp) cpu_irq_enable(); - if (ddaRingAddPointer == savedDdaRingAddPointer) - { - return false; // we can't skip any moves - } - - // We are going to skip some moves. Get the end coordinate of the previous move. + // We may be going to skip some moves. Get the end coordinate of the previous move. DDA * const prevDda = ddaRingAddPointer->GetPrevious(); const size_t numVisibleAxes = reprap.GetGCodes().GetVisibleAxes(); for (size_t axis = 0; axis < numVisibleAxes; ++axis) @@ -447,15 +444,22 @@ bool Move::PausePrint(RestorePoint& rp) InverseAxisAndBedTransform(rp.moveCoords, prevDda->GetXAxes(), prevDda->GetYAxes()); // we assume that xAxes hasn't changed between the moves - dda = ddaRingAddPointer; - rp.feedRate = dda->GetRequestedSpeed(); - rp.virtualExtruderPosition = dda->GetVirtualExtruderPosition(); - rp.filePos = dda->GetFilePosition(); + rp.proportionDone = ddaRingAddPointer->GetProportionDone(false); // get the proportion of the current multi-segment move that has been completed #if SUPPORT_IOBITS rp.ioBits = dda->GetIoBits(); #endif + if (ddaRingAddPointer == savedDdaRingAddPointer) + { + return false; // we can't skip any moves + } + + dda = ddaRingAddPointer; + rp.feedRate = dda->GetRequestedSpeed(); + rp.virtualExtruderPosition = dda->GetVirtualExtruderPosition(); + rp.filePos = dda->GetFilePosition(); + // Free the DDAs for the moves we are going to skip do { @@ -468,6 +472,8 @@ bool Move::PausePrint(RestorePoint& rp) return true; } +#if HAS_VOLTAGE_MONITOR + // Pause the print immediately, returning true if we were able to skip or abort any moves and setting up to the move we aborted bool Move::LowPowerPause(RestorePoint& rp) { @@ -514,7 +520,7 @@ bool Move::LowPowerPause(RestorePoint& rp) rp.feedRate = dda->GetRequestedSpeed(); rp.virtualExtruderPosition = dda->GetVirtualExtruderPosition(); rp.filePos = dda->GetFilePosition(); - rp.proportionDone = dda->GetProportionDone(); // store how much of the complete multi-segment move's extrusion has been done + rp.proportionDone = dda->GetProportionDone(abortedMove); // store how much of the complete multi-segment move's extrusion has been done #if SUPPORT_IOBITS rp.ioBits = dda->GetIoBits(); @@ -542,6 +548,8 @@ bool Move::LowPowerPause(RestorePoint& rp) return true; } +#endif + #if 0 // For debugging extern uint32_t sqSum1, sqSum2, sqCount, sqErrors, lastRes1, lastRes2; diff --git a/src/Movement/Move.h b/src/Movement/Move.h index 86eb6ab3..195226a9 100644 --- a/src/Movement/Move.h +++ b/src/Movement/Move.h @@ -99,7 +99,9 @@ public: void PrintCurrentDda() const; // For debugging bool PausePrint(RestorePoint& rp); // Pause the print as soon as we can, returning true if we were able to +#if HAS_VOLTAGE_MONITOR bool LowPowerPause(RestorePoint& rp); // Pause the print immediately, returning true if we were able to +#endif bool NoLiveMovement() const; // Is a move running, or are there any queued? diff --git a/src/Platform.cpp b/src/Platform.cpp index 130fa306..068f8cbc 100644 --- a/src/Platform.cpp +++ b/src/Platform.cpp @@ -568,7 +568,8 @@ void Platform::Init() } } - temperatureShutdownDrivers = temperatureWarningDrivers = shortToGroundDrivers = openLoadDrivers = stalledDrivers = previousStalledDrivers = 0; + temperatureShutdownDrivers = temperatureWarningDrivers = shortToGroundDrivers = openLoadDrivers = stalledDrivers = 0; + stalledDriversToLog = stalledDriversToPause = stalledDriversToRehome = 0; onBoardDriversFanRunning = offBoardDriversFanRunning = false; autoSaveEnabled = false; autoSaveState = AutoSaveState::starting; @@ -662,7 +663,7 @@ void Platform::Init() AnalogInEnableChannel(vInMonitorAdcChannel, true); currentVin = highestVin = 0; lowestVin = 9999; - numUnderVoltageEvents = numOverVoltageEvents = 0; + numUnderVoltageEvents = previousUnderVoltageEvents = numOverVoltageEvents = previousOverVoltageEvents = 0; #endif // Clear the spare pin configuration @@ -1385,7 +1386,7 @@ void Platform::Spin() } else { - // Poll one TMC2660 for temperature warning or temperature shutdown + // Check one TMC2660 for temperature warning or temperature shutdown if (enableValues[nextDriveToPoll] >= 0) // don't poll driver if it is flagged "no poll" { const uint32_t stat = SmartDrivers::GetAccumulatedStatus(nextDriveToPoll, 0); @@ -1425,10 +1426,47 @@ void Platform::Spin() } if ((stat & TMC_RR_SG) != 0) { + if ((stalledDrivers & mask) == 0) + { + // This stall is new and we are printing, so check whether we need to perform some action in response to the stall + if ((rehomeOnStallDrivers & mask) != 0) + { + stalledDriversToRehome |= mask; + } + else if ((pauseOnStallDrivers & mask) != 0) + { + stalledDriversToPause |= mask; + } + else if ((logOnStallDrivers & mask) != 0) + { + stalledDriversToLog |= mask; + } + } stalledDrivers |= mask; } - // We don't clear any stalled driver bits here because a stall may be transient + else + { + stalledDrivers &= ~mask; + } } + + // Action any pause or rehome actions due to motor stalls. This may have to be done more than once. + if (stalledDriversToRehome != 0) + { + if (reprap.GetGCodes().ReHomeOnStall(stalledDriversToRehome)) + { + stalledDriversToRehome = 0; + } + } + else if (stalledDriversToPause != 0) + { + if (reprap.GetGCodes().PauseOnStall(stalledDriversToPause)) + { + stalledDriversToPause = 0; + } + } + + // Advance drive number ready for next time ++nextDriveToPoll; if (nextDriveToPoll == numSmartDrivers) { @@ -1489,27 +1527,31 @@ void Platform::Spin() } // Check for stalled drivers that need to be reported and logged - const DriversBitmap currentStalledDrivers = stalledDrivers & logOnStallDrivers; - DriversBitmap newStalledDrivers = currentStalledDrivers & ~previousStalledDrivers; - if (newStalledDrivers != 0 && reprap.GetGCodes().IsReallyPrinting()) + if (stalledDriversToLog != 0 && reprap.GetGCodes().IsReallyPrinting()) { - // Report the newly-stalled drivers scratchString.Clear(); - for (unsigned int driver = 0; newStalledDrivers != 0; ++driver) - { - if ((newStalledDrivers & 1) != 0) - { - scratchString.catf(" %u", driver); - } - newStalledDrivers >>= 1; - } + ListDrivers(scratchString, stalledDriversToLog); + stalledDriversToLog = 0; float liveCoordinates[DRIVES]; reprap.GetMove().LiveCoordinates(liveCoordinates, reprap.GetCurrentXAxes(), reprap.GetCurrentYAxes()); - MessageF(WarningMessage, "Driver(s)%s stalled at Z height %.1f", scratchString.Pointer(), (double)liveCoordinates[Z_AXIS]); + MessageF(WarningMessage, "Driver(s)%s stalled at Z height %.2f", scratchString.Pointer(), (double)liveCoordinates[Z_AXIS]); + reported = true; + } +#endif + +#if HAS_VOLTAGE_MONITOR + if (numOverVoltageEvents != previousOverVoltageEvents) + { + MessageF(WarningMessage, "VIN over-voltage event (%.1fV)", (double)GetCurrentPowerVoltage()); + previousOverVoltageEvents = numOverVoltageEvents; + reported = true; + } + if (numUnderVoltageEvents != previousUnderVoltageEvents) + { + MessageF(WarningMessage, "VIN under-voltage event (%.1fV)", (double)GetCurrentPowerVoltage()); + previousUnderVoltageEvents = numUnderVoltageEvents; reported = true; } - previousStalledDrivers = currentStalledDrivers; - stalledDrivers = 0; #endif #ifdef DUET_NG @@ -1569,7 +1611,7 @@ void Platform::Spin() // Flush the log file it it is time. This may take some time, so do it last. if (logger != nullptr) { - logger->Flush(); + logger->Flush(false); } ClassReport(longWait); @@ -1592,11 +1634,30 @@ void Platform::ReportDrivers(DriversBitmap whichDrivers, const char* text, bool& } whichDrivers >>= 1; } - MessageF(GenericMessage, "%s\n", scratchString.Pointer()); + MessageF(WarningMessage, "%s\n", scratchString.Pointer()); reported = true; } } +// Return true if any motor driving this axis or extruder is stalled +bool Platform::AnyMotorStalled(size_t drive) const +{ + const size_t numAxes = reprap.GetGCodes().GetTotalAxes(); + if (drive < numAxes) + { + for (size_t i = 0; i < axisDrivers[drive].numDrivers; ++i) + { + if ((SmartDrivers::GetLiveStatus(axisDrivers[drive].driverNumbers[i]) & TMC_RR_SG) != 0) + { + return true; + } + } + return false; + } + + return (SmartDrivers::GetLiveStatus(extruderDrivers[drive - numAxes]) & TMC_RR_SG) != 0; +} + #endif #if HAS_VOLTAGE_MONITOR @@ -2278,7 +2339,7 @@ void Platform::SetHeater(size_t heater, float power) { if (heatOnPins[heater] != NoPin) { - const uint16_t freq = (reprap.GetHeat().UseSlowPwm(heater)) ? SlowHeaterPwmFreq : NormalHeaterPwmFreq; + const uint16_t freq = (reprap.GetHeat().IsBedOrChamberHeater(heater)) ? SlowHeaterPwmFreq : NormalHeaterPwmFreq; const float pwm = #if ACTIVE_LOW_HEAT_ON 1.0 - power; @@ -2341,12 +2402,40 @@ EndStopHit Platform::Stopped(size_t drive) const #if HAS_SMART_DRIVERS case EndStopInputType::motorStall: - for (size_t i = 0; i < axisDrivers[drive].numDrivers; ++i) { - if ((SmartDrivers::GetLiveStatus(axisDrivers[drive].driverNumbers[i]) & TMC_RR_SG) != 0) + bool motorIsStalled; + switch (reprap.GetMove().GetKinematics().GetKinematicsType()) { - return (endStopPos[drive] == EndStopPosition::highEndStop) ? EndStopHit::highHit : EndStopHit::lowHit; + case KinematicsType::coreXY: + // Both X and Y motors are involved in homing X or Y + motorIsStalled = (drive == X_AXIS || drive == Y_AXIS) + ? AnyMotorStalled(X_AXIS) || AnyMotorStalled(Y_AXIS) + : AnyMotorStalled(drive); + break; + + case KinematicsType::coreXYU: + // Both X and Y motors are involved in homing X or Y, and both U and V motors are involved in homing U + motorIsStalled = (drive == X_AXIS || drive == Y_AXIS) + ? AnyMotorStalled(X_AXIS) || AnyMotorStalled(Y_AXIS) + : (drive == U_AXIS) + ? AnyMotorStalled(U_AXIS) || AnyMotorStalled(V_AXIS) + : AnyMotorStalled(drive); + break; + + case KinematicsType::coreXZ: + // Both X and Z motors are involved in homing X or Z + motorIsStalled = (drive == X_AXIS || drive == Z_AXIS) + ? AnyMotorStalled(X_AXIS) || AnyMotorStalled(Z_AXIS) + : AnyMotorStalled(drive); + break; + + default: + motorIsStalled = AnyMotorStalled(drive); + break; } + return (!motorIsStalled) ? EndStopHit::noStop + : (endStopPos[drive] == EndStopPosition::highEndStop) ? EndStopHit::highHit + : EndStopHit::lowHit; } break; #endif @@ -3396,10 +3485,15 @@ bool Platform::AtxPower() const void Platform::SetAtxPower(bool on) { + if (!on && logger != nullptr) + { + logger->LogMessage(realTime, "Power off commanded"); + logger->Flush(true); + // We don't call logger->Stop() here because we don't now whether turning ofrf the power will work + } IoPort::WriteDigital(ATX_POWER_PIN, on); } - void Platform::SetPressureAdvance(size_t extruder, float factor) { if (extruder < MaxExtruders) @@ -3812,6 +3906,11 @@ void Platform::GetPowerVoltages(float& minV, float& currV, float& maxV) const maxV = AdcReadingToPowerVoltage(highestVin); } +float Platform::GetCurrentPowerVoltage() const +{ + return AdcReadingToPowerVoltage(currentVin); +} + #endif #if HAS_SMART_DRIVERS diff --git a/src/Platform.h b/src/Platform.h index 84c44edb..8c10fec9 100644 --- a/src/Platform.h +++ b/src/Platform.h @@ -543,6 +543,7 @@ public: #if HAS_VOLTAGE_MONITOR // Power in voltage void GetPowerVoltages(float& minV, float& currV, float& maxV) const; + float GetCurrentPowerVoltage() const; bool IsPowerOk() const; void DisableAutoSave(); void EnableAutoSave(float saveVoltage, float resumeVoltage); @@ -602,6 +603,7 @@ private: #if HAS_SMART_DRIVERS void ReportDrivers(DriversBitmap whichDrivers, const char* text, bool& reported); + bool AnyMotorStalled(size_t drive) const pre(drive < DRIVES); #endif // These are the structures used to hold our non-volatile data. @@ -720,7 +722,7 @@ private: size_t numSmartDrivers; // the number of TMC2660 drivers we have, the remaining are simple enable/step/dir drivers DriversBitmap logOnStallDrivers, pauseOnStallDrivers, rehomeOnStallDrivers; DriversBitmap temperatureShutdownDrivers, temperatureWarningDrivers, shortToGroundDrivers, openLoadDrivers, stalledDrivers; - DriversBitmap previousStalledDrivers; + DriversBitmap stalledDriversToLog, stalledDriversToPause, stalledDriversToRehome; uint8_t nextDriveToPoll; bool driversPowered; bool onBoardDriversFanRunning; // true if a fan is running to cool the on-board drivers @@ -858,8 +860,8 @@ private: AnalogChannelNumber vInMonitorAdcChannel; volatile uint16_t currentVin, highestVin, lowestVin; uint16_t autoPauseReading, autoResumeReading; - uint32_t numUnderVoltageEvents; - volatile uint32_t numOverVoltageEvents; + uint32_t numUnderVoltageEvents, previousUnderVoltageEvents; + volatile uint32_t numOverVoltageEvents, previousOverVoltageEvents; bool autoSaveEnabled; enum class AutoSaveState : uint8_t diff --git a/src/PortControl.cpp b/src/PortControl.cpp index c3f1d26c..44cbdbdd 100644 --- a/src/PortControl.cpp +++ b/src/PortControl.cpp @@ -78,14 +78,14 @@ bool PortControl::Configure(GCodeBuffer& gb, StringRef& reply) gb.GetLongArray(portNumbers, numPorts); for (size_t i = 0; i < numPorts; ++i) { - long pnum = portNumbers[i]; + const long pnum = portNumbers[i]; if (pnum < 0 || pnum > HighestLogicalPin) { reply.printf("Port number %ld out of range", pnum); return true; } IoPort& pm = portMap[i]; - if (!pm.Set(pnum, PinAccess::write)) + if (!pm.Set((LogicalPin)pnum, PinAccess::write)) { reply.printf("Port number %ld is not available", pnum); return true; diff --git a/src/RepRap.cpp b/src/RepRap.cpp index a7033656..ee95198c 100644 --- a/src/RepRap.cpp +++ b/src/RepRap.cpp @@ -308,13 +308,16 @@ void RepRap::EmergencyStop() void RepRap::SetDebug(Module m, bool enable) { - if (enable) + if (m < numModules) { - debug |= (1u << m); - } - else - { - debug &= ~(1u << m); + if (enable) + { + debug |= (1u << m); + } + else + { + debug &= ~(1u << m); + } } PrintDebug(); } @@ -404,35 +407,27 @@ void RepRap::DeleteTool(Tool* tool) platform->UpdateConfiguredHeaters(); } +// Select the specified tool, putting the existing current tool into standby void RepRap::SelectTool(int toolNumber, bool simulating) { - Tool* tool = toolList; - - while(tool != nullptr) + Tool* const newTool = GetTool(toolNumber); + if (!simulating) { - if (tool->Number() == toolNumber) + if (currentTool != nullptr && currentTool != newTool) { - if (!simulating) - { - tool->Activate(currentTool); - } - currentTool = tool; - return; + currentTool->Standby(); + } + if (newTool != nullptr) + { + newTool->Activate(); } - tool = tool->Next(); - } - - // Selecting a non-existent tool is valid. It sets them all to standby. - if (currentTool != nullptr && !simulating) - { - StandbyTool(currentTool->Number()); } - currentTool = nullptr; + currentTool = newTool; } void RepRap::PrintTool(int toolNumber, StringRef& reply) const { - Tool* tool = GetTool(toolNumber); + const Tool* const tool = GetTool(toolNumber); if (tool != nullptr) { tool->Print(reply); @@ -445,7 +440,7 @@ void RepRap::PrintTool(int toolNumber, StringRef& reply) const void RepRap::StandbyTool(int toolNumber) { - Tool* tool = GetTool(toolNumber); + Tool* const tool = GetTool(toolNumber); if (tool != nullptr) { tool->Standby(); @@ -474,6 +469,12 @@ Tool* RepRap::GetTool(int toolNumber) const return nullptr; // Not an error } +// Return the current tool number, or -1 if no tool selected +int RepRap::GetCurrentToolNumber() const +{ + return (currentTool == nullptr) ? -1 : currentTool->Number(); +} + // Get the current tool, or failing that the default tool. May return nullptr if we can't // Called when a M104 or M109 command doesn't specify a tool number. Tool* RepRap::GetCurrentOrDefaultTool() const @@ -497,9 +498,9 @@ void RepRap::SetToolVariables(int toolNumber, const float* standbyTemperatures, bool RepRap::IsHeaterAssignedToTool(int8_t heater) const { - for(Tool *tool = toolList; tool != nullptr; tool = tool->Next()) + for (Tool *tool = toolList; tool != nullptr; tool = tool->Next()) { - for(size_t i = 0; i < tool->HeaterCount(); i++) + for (size_t i = 0; i < tool->HeaterCount(); i++) { if (tool->Heater(i) == heater) { @@ -631,8 +632,7 @@ OutputBuffer *RepRap::GetStatusResponse(uint8_t type, ResponseSource source) } // Current tool number - const int toolNumber = (currentTool == nullptr) ? -1 : currentTool->Number(); - response->catf("]},\"currentTool\":%d", toolNumber); + response->catf("]},\"currentTool\":%d", GetCurrentToolNumber()); // Output notifications { @@ -1313,8 +1313,7 @@ OutputBuffer *RepRap::GetLegacyStatusResponse(uint8_t type, int seq) response->catf(",\"babystep\":%.03f", (double)(gCodes->GetBabyStepOffset())); // Send the current tool number - const int toolNumber = (currentTool == nullptr) ? 0 : currentTool->Number(); - response->catf(",\"tool\":%d", toolNumber); + response->catf(",\"tool\":%d", GetCurrentToolNumber()); // Send the Z probe value const int v0 = platform->GetZProbeReading(); diff --git a/src/RepRap.h b/src/RepRap.h index 8d77df7c..a163f480 100644 --- a/src/RepRap.h +++ b/src/RepRap.h @@ -59,6 +59,7 @@ public: void SelectTool(int toolNumber, bool simulating); void StandbyTool(int toolNumber); Tool* GetCurrentTool() const; + int GetCurrentToolNumber() const; Tool* GetTool(int toolNumber) const; Tool* GetCurrentOrDefaultTool() const; const Tool* GetFirstTool() const { return toolList; } // Return the lowest-numbered tool diff --git a/src/RepRapFirmware.cpp b/src/RepRapFirmware.cpp index 904cb598..8093866a 100644 --- a/src/RepRapFirmware.cpp +++ b/src/RepRapFirmware.cpp @@ -185,7 +185,7 @@ const char *moduleName[] = "PortControl", "DuetExpansion", "FilamentSensors", - "?", + "WiFi", "none" }; @@ -289,4 +289,17 @@ void SafeStrncat(char *dst, const char *src, size_t length) dst[length - 1] = 0; } +// Append a list of driver numbers to a string, with a space before each one +void ListDrivers(const StringRef& str, DriversBitmap drivers) +{ + for (unsigned int d = 0; drivers != 0; ++d) + { + if ((drivers & 1) != 0) + { + scratchString.catf(" %u", d); + } + drivers >>= 1; + } +} + // End diff --git a/src/RepRapFirmware.h b/src/RepRapFirmware.h index 3a1037c6..11f3a7b5 100644 --- a/src/RepRapFirmware.h +++ b/src/RepRapFirmware.h @@ -31,6 +31,7 @@ Licence: GPL #include "Core.h" #include "Configuration.h" #include "Pins.h" + #include "Libraries/General/StringRef.h" // Module numbers and names, used for diagnostics and debug @@ -110,6 +111,8 @@ int StringContains(const char* string, const char* match); void SafeStrncpy(char *dst, const char *src, size_t length) pre(length != 0); void SafeStrncat(char *dst, const char *src, size_t length) pre(length != 0); +void ListDrivers(const StringRef& str, DriversBitmap drivers); + // Macro to assign an array from an initialiser list #define ARRAY_INIT(_dest, _init) static_assert(sizeof(_dest) == sizeof(_init), "Incompatible array types"); memcpy(_dest, _init, sizeof(_init)); @@ -194,6 +197,8 @@ extern StringRef scratchString; // Common definitions used by more than one module const size_t XYZ_AXES = 3; // The number of Cartesian axes const size_t X_AXIS = 0, Y_AXIS = 1, Z_AXIS = 2, E0_AXIS = 3; // The indices of the Cartesian axes in drive arrays +const size_t CoreXYU_AXES = 5; // The number of axes in a CoreXYU machine +const size_t U_AXIS = 3, V_AXIS = 4; // The indices of the U and V motors in a CoreXYU machine (needed by Platform) // Common conversion factors const float MinutesToSeconds = 60.0; @@ -211,11 +216,7 @@ typedef uint32_t FilePosition; const FilePosition noFilePosition = 0xFFFFFFFF; // Interrupt priorities - must be chosen with care! 0 is the highest priority, 15 is the lowest. -#if SAM4E || SAM4S -const uint32_t NvicPriorityWatchdog = 0; // watchdog has highest priority (SAM4 only) -#endif - -const uint32_t NvicPriorityUart = 1; // UART is next to avoid character loss +const uint32_t NvicPriorityUart = 1; // UART is highest to avoid character loss (it has only a 1-character receive buffer) const uint32_t NvicPriorityDriversUsart = 2; // USART used to control and monitor the TMC2660 drivers const uint32_t NvicPrioritySystick = 3; // systick kicks the watchdog and starts the ADC conversions, so must be quite high const uint32_t NvicPriorityPins = 4; // priority for GPIO pin interrupts - filament sensors must be higher than step diff --git a/src/Tools/Tool.cpp b/src/Tools/Tool.cpp index 76628141..f4776eb2 100644 --- a/src/Tools/Tool.cpp +++ b/src/Tools/Tool.cpp @@ -139,7 +139,7 @@ Tool * Tool::freelist = nullptr; } } -void Tool::Print(StringRef& reply) +void Tool::Print(StringRef& reply) const { reply.printf("Tool %d - ", myNumber); if (!StringEquals(name, "")) @@ -301,35 +301,25 @@ bool Tool::AllHeatersAtHighTemperature(bool forExtrusion) const return true; } -void Tool::Activate(Tool* currentlyActive) +void Tool::Activate() { - if (state != ToolState::active) + for (size_t heater = 0; heater < heaterCount; heater++) { - if (currentlyActive != nullptr && currentlyActive != this) - { - currentlyActive->Standby(); - } - for (size_t heater = 0; heater < heaterCount; heater++) - { - reprap.GetHeat().SetActiveTemperature(heaters[heater], activeTemperatures[heater]); - reprap.GetHeat().SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]); - reprap.GetHeat().Activate(heaters[heater]); - } - state = ToolState::active; + reprap.GetHeat().SetActiveTemperature(heaters[heater], activeTemperatures[heater]); + reprap.GetHeat().SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]); + reprap.GetHeat().Activate(heaters[heater]); } + state = ToolState::active; } void Tool::Standby() { - if (state != ToolState::standby) + for (size_t heater = 0; heater < heaterCount; heater++) { - for (size_t heater = 0; heater < heaterCount; heater++) - { - reprap.GetHeat().SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]); - reprap.GetHeat().Standby(heaters[heater], this); - } - state = ToolState::standby; + reprap.GetHeat().SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]); + reprap.GetHeat().Standby(heaters[heater], this); } + state = ToolState::standby; } void Tool::SetVariables(const float* standby, const float* active) diff --git a/src/Tools/Tool.h b/src/Tools/Tool.h index 9f06e908..5e56b1d0 100644 --- a/src/Tools/Tool.h +++ b/src/Tools/Tool.h @@ -63,7 +63,7 @@ public: const float* GetMix() const; float MaxFeedrate() const; float InstantDv() const; - void Print(StringRef& reply); + void Print(StringRef& reply) const; AxesBitmap GetXAxisMap() const { return xMapping; } AxesBitmap GetYAxisMap() const { return yMapping; } FansBitmap GetFanMapping() const { return fanMapping; } @@ -75,7 +75,7 @@ public: friend class RepRap; protected: - void Activate(Tool* currentlyActive); + void Activate(); void Standby(); void FlagTemperatureFault(int8_t dudHeater); void ClearTemperatureFault(int8_t wasDudHeater); diff --git a/src/Version.h b/src/Version.h index d419161c..a6ce7a3f 100644 --- a/src/Version.h +++ b/src/Version.h @@ -9,11 +9,11 @@ #define SRC_VERSION_H_ #ifndef VERSION -# define VERSION "1.20beta7" +# define VERSION "1.20beta8" #endif #ifndef DATE -# define DATE "2017-11-11" +# define DATE "2017-11-17" #endif #define AUTHORS "reprappro, dc42, chrishamm, t3p3, dnewman" |