Version 1.09s beta 4

Enhancements:
- Implemented M143 and M350
- Wait until movement finished when processing M906
- Allow for additional axes in M906 reporting code
- Added support for external drivers module
- Aux2 device support is now conditional
- Added separate error code for temperature above safety limit
Bug fixes:
- Fixed spurious error report when processing corrupt input line
- When there is a temperature error, return the correct error code
- Update the overheat ADC value when changing thermistor parameters
- Fixed occasional divide by zero problem in PrintMonitor that led to
AJAX errors
- Cold extrusion prevention only checks the active tool, to allow the
same extruder and heater to be configured in multiple tools
- If extrusion is prevented because of a temperature fault, display a
message instead of silently preventing extrusion
This commit is contained in:
David Crocker 2016-03-09 14:34:01 +00:00
parent a5722accc7
commit bac9eb516e
15 changed files with 498 additions and 252 deletions

View file

@ -26,17 +26,20 @@ Licence: GPL
#define NAME "RepRapFirmware"
#ifndef VERSION
#define VERSION "1.09r-dc42"
#define VERSION "1.09s-dc42-beta4"
#endif
#ifndef DATE
#define DATE "2016-01-16"
#define DATE "2016-03-08"
#endif
#define AUTHORS "reprappro, dc42, zpl, t3p3, dnewman"
#define FLASH_SAVE_ENABLED (1)
//#define EXTERNAL_DRIVERS (1)
//#define FIRST_EXTERNAL_DRIVE (4)
// Other firmware that we might switch to be compatible with.
enum Compatibility
@ -81,9 +84,9 @@ const float HOT_ENOUGH_TO_EXTRUDE = 160.0; // Celsius
const float HOT_ENOUGH_TO_RETRACT = 90.0; // Celsius
const float TIME_TO_HOT = 150.0; // Seconds
const uint8_t MAX_BAD_TEMPERATURE_COUNT = 6; // Number of bad temperature samples before a heater fault is reported
const uint8_t MAX_BAD_TEMPERATURE_COUNT = 4; // Number of bad temperature samples permitted before a heater fault is reported
const float BAD_LOW_TEMPERATURE = -10.0; // Celsius
const float BAD_HIGH_TEMPERATURE = 300.0; // Celsius
const float DEFAULT_TEMPERATURE_LIMIT = 300.0; // Celsius
const float HOT_END_FAN_TEMPERATURE = 45.0; // Temperature at which a thermostatic hot end fan comes on
const float BAD_ERROR_TEMPERATURE = 2000.0; // must exceed BAD_HIGH_TEMPERATURE

21
ExternalDrivers.h Normal file
View file

@ -0,0 +1,21 @@
/*
* ExternalDrivers.h
*
* Created on: 23 Jan 2016
* Author: David
*/
#ifndef EXTERNALDRIVERS_H_
#define EXTERNALDRIVERS_H_
namespace ExternalDrivers
{
void Init();
void SetCurrent(size_t drive, float current);
void EnableDrive(size_t drive, bool en);
uint32_t GetStatus(size_t drive);
bool SetMicrostepping(size_t drive, int microsteps, int mode);
unsigned int GetMicrostepping(size_t drive, bool& interpolation);
};
#endif /* EXTERNALDRIVERS_H_ */

View file

@ -65,12 +65,14 @@ bool GCodeBuffer::Put(char c)
// Deal with line numbers and checksums
if (Seen('*'))
{
int csSent = GetIValue();
int csHere = CheckSum();
Seen('N');
const int csSent = GetIValue();
const int csHere = CheckSum();
if (csSent != csHere)
{
snprintf(gcodeBuffer, GCODE_LENGTH, "M998 P%d", GetIValue());
if (Seen('N'))
{
snprintf(gcodeBuffer, GCODE_LENGTH, "M998 P%d", GetIValue());
}
Init();
return true;
}

View file

@ -3550,8 +3550,19 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
}
break;
case 143: // Set temperature limit
if (gb->Seen('S'))
{
platform->SetTemperatureLimit(gb->GetFValue());
}
else
{
reply.printf("Temperature limit is %.1fC", platform->GetTemperatureLimit());
}
break;
case 144: // Set bed to standby
#if BED_HEATER != -1
#if BED_HEATER >= 0
reprap.GetHeat()->Standby(BED_HEATER);
#else
reply.copy("Hot bed is not present!");
@ -3559,14 +3570,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
#endif
break;
// case 160: //number of mixing filament drives TODO: With tools defined, is this needed?
// if(gb->Seen('S'))
// {
// platform->SetMixingDrives(gb->GetIValue());
// }
// break;
case 190: // Deprecated...
case 190: // Set bed temperature and wait
if (!AllMovesAreFinishedAndMoveBufferIsLoaded()) // tell Move not to wait for more moves
{
return false;
@ -3818,9 +3822,75 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
SetHeaterParameters(gb, reply);
break;
case 350: // Set/report microstepping
if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
{
return false;
}
{
// interp is current an int not a bool, because we use special values of interp to set the chopper control register
int interp = 0;
if (gb->Seen('I'))
{
interp = gb->GetIValue();
}
bool seen = false;
for (size_t axis = 0; axis < AXES; axis++)
{
if (gb->Seen(axisLetters[axis]))
{
seen = true;
int microsteps = gb->GetIValue();
if (!platform->SetMicrostepping(axis, microsteps, interp))
{
platform->MessageF(GENERIC_MESSAGE, "Drive %c does not support %dx microstepping%s\n",
axisLetters[axis], microsteps, (interp) ? " with interpolation" : "");
}
}
}
if (gb->Seen(extrudeLetter))
{
seen = true;
long eVals[DRIVES - AXES];
size_t eCount = DRIVES - AXES;
gb->GetLongArray(eVals, eCount);
for (size_t e = 0; e < eCount; e++)
{
if (!platform->SetMicrostepping(AXES + e, (int)eVals[e], interp))
{
platform->MessageF(GENERIC_MESSAGE, "Drive E%u does not support %dx microstepping%s\n",
e, (int)eVals[e], (interp) ? " with interpolation" : "");
}
}
}
if (!seen)
{
reply.copy("Microstepping - ");
for (size_t axis = 0; axis < AXES; ++axis)
{
bool interp;
int microsteps = platform->GetMicrostepping(axis, interp);
reply.catf("%c:%d%s, ", axisLetters[axis], microsteps, (interp) ? "(on)" : "");
}
reply.cat("E");
for (size_t drive = AXES; drive < DRIVES; drive++)
{
bool interp;
int microsteps = platform->GetMicrostepping(drive, interp);
reply.catf(":%d%s", microsteps, (interp) ? "(on)" : "");
}
}
}
break;
case 400: // Wait for current moves to finish
if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
{
return false;
}
break;
case 404: // Filament width and nozzle diameter
@ -4610,7 +4680,7 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
break;
#endif
case 579: // Scale Cartesian axes (for Delta configurations)
case 579: // Scale Cartesian axes (mostly for Delta configurations)
{
bool seen = false;
for(size_t axis = 0; axis < AXES; axis++)
@ -4820,6 +4890,10 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
break;
case 906: // Set/report Motor currents
if (!AllMovesAreFinishedAndMoveBufferIsLoaded())
{
return false;
}
{
bool seen = false;
for (size_t axis = 0; axis < AXES; axis++)
@ -4856,13 +4930,17 @@ bool GCodes::HandleMcode(GCodeBuffer* gb, StringRef& reply)
if (!seen)
{
reply.printf("Axis currents (mA) - X:%d, Y:%d, Z:%d, E:", (int) platform->MotorCurrent(X_AXIS),
(int) platform->MotorCurrent(Y_AXIS), (int) platform->MotorCurrent(Z_AXIS));
reply.copy("Axis currents (mA) - ");
for (size_t axis = 0; axis < AXES; ++axis)
{
reply.catf("%c:%d, ", axisLetters[axis], (int) platform->MotorCurrent(axis));
}
reply.cat("E");
for (size_t drive = AXES; drive < DRIVES; drive++)
{
reply.catf("%d%c", (int) platform->MotorCurrent(drive), (drive < DRIVES - 1) ? ':' : ',');
reply.catf(":%d", (int) platform->MotorCurrent(drive));
}
reply.catf(" idle factor %d", (int)(platform->GetIdleCurrentFactor() * 100.0));
reply.catf(", idle factor %d%%", (int)(platform->GetIdleCurrentFactor() * 100.0));
}
}
break;

View file

@ -163,8 +163,8 @@ void PID::Spin()
// Always know our temperature, regardless of whether we have been switched on or not
Platform::TempError err = Platform::TempError::errOpen; // Initially assign an error; call should update but if it doesn't, we'll treat as an error
temperature = platform->GetTemperature(heater, &err); // In the event of an error, err is set and BAD_ERROR_TEMPERATURE is returned
Platform::TempError err = Platform::TempError::errOk; // assume no error
temperature = platform->GetTemperature(heater, &err); // in the event of an error, err is set and BAD_ERROR_TEMPERATURE is returned
// If we're not switched on, or there's a fault, turn the power off and go home.
// If we're not switched on, then nothing is using us. This probably means that
@ -180,7 +180,16 @@ void PID::Spin()
// We are switched on. Check for faults. Temperature silly-low or silly-high mean open-circuit
// or shorted thermistor respectively.
if (temperature < BAD_LOW_TEMPERATURE || temperature > BAD_HIGH_TEMPERATURE)
if (temperature < BAD_LOW_TEMPERATURE)
{
err = Platform::TempError::errOpen;
}
else if (temperature > platform->GetTemperatureLimit())
{
err = Platform::TempError::errTooHigh;
}
if (err != Platform::TempError::errOk)
{
if (platform->DoThermistorAdc(heater) || !(Platform::TempErrorPermanent(err)))
{
@ -209,7 +218,7 @@ void PID::Spin()
}
else
{
badTemperatureCount = 0;
badTemperatureCount = 0;
}
// Now check how long it takes to warm up. If too long, maybe the thermistor is not in contact with the heater
@ -303,6 +312,75 @@ void PID::Spin()
// debugPrintf("Heater %d: e=%f, P=%f, I=%f, d=%f, r=%f\n", heater, error, pp.kP*error, temp_iState, temp_dState, result);
}
void PID::SetActiveTemperature(float t)
{
if (t > platform->GetTemperatureLimit())
{
platform->MessageF(GENERIC_MESSAGE, "Error: Temperature %.1f too high for heater %d!\n", t, heater);
}
SwitchOn();
activeTemperature = t;
}
void PID::SetStandbyTemperature(float t)
{
if (t > platform->GetTemperatureLimit())
{
platform->MessageF(GENERIC_MESSAGE, "Error: Temperature %.1f too high for heater %d!\n", t, heater);
}
SwitchOn();
standbyTemperature = t;
}
void PID::Activate()
{
if (temperatureFault)
{
return;
}
SwitchOn();
active = true;
if (!heatingUp)
{
timeSetHeating = platform->Time();
}
heatingUp = activeTemperature > temperature;
}
void PID::Standby()
{
if (temperatureFault)
{
return;
}
SwitchOn();
active = false;
if (!heatingUp)
{
timeSetHeating = platform->Time();
}
heatingUp = standbyTemperature > temperature;
}
void PID::ResetFault()
{
temperatureFault = false;
timeSetHeating = platform->Time(); // otherwise we will get another timeout immediately
badTemperatureCount = 0;
}
void PID::SwitchOff()
{
SetHeater(0.0);
active = false;
switchedOff = true;
heatingUp = false;
}
float PID::GetAveragePWM() const
{
return averagePWM * invHeatPwmAverageCount;

69
Heat.h
View file

@ -137,33 +137,11 @@ inline bool PID::Active() const
return active;
}
inline void PID::SetActiveTemperature(float t)
{
if (t > BAD_HIGH_TEMPERATURE)
{
platform->MessageF(GENERIC_MESSAGE, "Error: Temperature %.1f too high for heater %d!\n", t, heater);
}
SwitchOn();
activeTemperature = t;
}
inline float PID::GetActiveTemperature() const
{
return activeTemperature;
}
inline void PID::SetStandbyTemperature(float t)
{
if (t > BAD_HIGH_TEMPERATURE)
{
platform->MessageF(GENERIC_MESSAGE, "Error: Temperature %.1f too high for heater %d!\n", t, heater);
}
SwitchOn();
standbyTemperature = t;
}
inline float PID::GetStandbyTemperature() const
{
return standbyTemperature;
@ -174,58 +152,11 @@ inline float PID::GetTemperature() const
return temperature;
}
inline void PID::Activate()
{
if (temperatureFault)
{
return;
}
SwitchOn();
active = true;
if (!heatingUp)
{
timeSetHeating = platform->Time();
}
heatingUp = activeTemperature > temperature;
}
inline void PID::Standby()
{
if (temperatureFault)
{
return;
}
SwitchOn();
active = false;
if (!heatingUp)
{
timeSetHeating = platform->Time();
}
heatingUp = standbyTemperature > temperature;
}
inline bool PID::FaultOccurred() const
{
return temperatureFault;
}
inline void PID::ResetFault()
{
temperatureFault = false;
timeSetHeating = platform->Time(); // otherwise we will get another timeout immediately
badTemperatureCount = 0;
}
inline void PID::SwitchOff()
{
SetHeater(0.0);
active = false;
switchedOff = true;
heatingUp = false;
}
inline bool PID::SwitchedOff() const
{
return switchedOff;

View file

@ -85,7 +85,7 @@ extern void pinModeNonDue(uint32_t ulPin, uint32_t ulMode, uint32_t debounceCuto
return;
}
const PinDescription& pinDesc = (ulPin >= X0) ? nonDuePinDescription[ulPin - X0] : g_APinDescription[ulPin];
const PinDescription& pinDesc = GetPinDescription(ulPin);
if (pinDesc.ulPinType == PIO_NOT_A_PIN)
{
return;
@ -153,7 +153,7 @@ extern void digitalWriteNonDue(uint32_t ulPin, uint32_t ulVal)
return;
}
const PinDescription& pinDesc = (ulPin >= X0) ? nonDuePinDescription[ulPin - X0] : g_APinDescription[ulPin];
const PinDescription& pinDesc = GetPinDescription(ulPin);
if (pinDesc.ulPinType != PIO_NOT_A_PIN)
{
if (ulVal) // we make use of the fact that LOW is zero and HIGH is nonzero
@ -179,7 +179,7 @@ extern int digitalReadNonDue( uint32_t ulPin )
return LOW;
}
const PinDescription& pinDesc = (ulPin >= X0) ? nonDuePinDescription[ulPin - X0] : g_APinDescription[ulPin];
const PinDescription& pinDesc = GetPinDescription(ulPin);
if (pinDesc.ulPinType == PIO_NOT_A_PIN)
{
return LOW ;
@ -191,7 +191,7 @@ extern int digitalReadNonDue( uint32_t ulPin )
// Build a short-form pin descriptor for a IO pin
OutputPin::OutputPin(unsigned int pin)
{
const PinDescription& pinDesc = (pin >= X0) ? nonDuePinDescription[pin - X0] : g_APinDescription[pin];
const PinDescription& pinDesc = GetPinDescription(pin);
pPort = pinDesc.pPort;
ulPin = pinDesc.ulPin;
}
@ -249,7 +249,7 @@ void analogWriteNonDue(uint32_t ulPin, uint32_t ulValue, uint16_t freq)
ulValue = 255;
}
const PinDescription& pinDesc = (ulPin >= X0) ? nonDuePinDescription[ulPin - X0] : g_APinDescription[ulPin];
const PinDescription& pinDesc = GetPinDescription(ulPin);
uint32_t attr = pinDesc.ulPinAttribute;
if ((attr & PIN_ATTR_ANALOG) == PIN_ATTR_ANALOG)
{

View file

@ -84,18 +84,22 @@ public:
void SetLow() const { pPort->PIO_CODR = ulPin; }
};
// struct used to hold the descriptions for the "non Arduino" pins.
// from the Arduino.h files
extern const PinDescription nonDuePinDescription[] ;
extern void pinModeNonDue(uint32_t ulPin, uint32_t ulMode, uint32_t debounceCutoff = 0); // NB only one debounce cutoff frequency can be set per PIO
extern void digitalWriteNonDue(uint32_t ulPin, uint32_t ulVal);
extern int digitalReadNonDue(uint32_t ulPin);
extern OutputPin getPioPin(uint32_t ulPin);
extern void analogWriteNonDue(uint32_t ulPin, uint32_t ulValue, uint16_t freq);
extern void analogOutputNonDue();
extern void hsmciPinsinit();
extern void ethPinsInit();
extern adc_channel_num_t PinToAdcChannel(int pin); // convert an analog pin number to an ADC channel
extern const PinDescription nonDuePinDescription[];
inline const PinDescription& GetPinDescription(uint32_t ulPin)
{
return (ulPin >= X0) ? nonDuePinDescription[ulPin - X0] : g_APinDescription[ulPin];
}
void pinModeNonDue(uint32_t ulPin, uint32_t ulMode, uint32_t debounceCutoff = 0); // NB only one debounce cutoff frequency can be set per PIO
void digitalWriteNonDue(uint32_t ulPin, uint32_t ulVal);
int digitalReadNonDue(uint32_t ulPin);
OutputPin getPioPin(uint32_t ulPin);
void analogWriteNonDue(uint32_t ulPin, uint32_t ulValue, uint16_t freq);
void analogOutputNonDue();
void hsmciPinsinit();
void ethPinsInit();
adc_channel_num_t PinToAdcChannel(int pin); // convert an analog pin number to an ADC channel
#endif /* SAM_NON_DUE_PIN_H */

View file

@ -22,6 +22,10 @@
#include "RepRapFirmware.h"
#include "DueFlashStorage.h"
#ifdef EXTERNAL_DRIVERS
#include "ExternalDrivers.h"
#endif
extern char _end;
extern "C" char *sbrk(int i);
@ -109,8 +113,7 @@ bool PidParameters::operator==(const PidParameters& other) const
Platform::Platform() :
autoSaveEnabled(false), board(DEFAULT_BOARD_TYPE), active(false), errorCodeBits(0),
fileStructureInitialised(false), tickState(0), debugCode(0),
messageString(messageStringBuffer, ARRAY_SIZE(messageStringBuffer))
fileStructureInitialised(false), tickState(0), debugCode(0)
{
// Output
auxOutput = new OutputStack();
@ -148,7 +151,9 @@ void Platform::Init()
SERIAL_MAIN_DEVICE.begin(baudRates[0]);
SERIAL_AUX_DEVICE.begin(baudRates[1]); // this can't be done in the constructor because the Arduino port initialisation isn't complete at that point
#ifdef SERIAL_AUX2_DEVICE
SERIAL_AUX2_DEVICE.begin(baudRates[2]);
#endif
// Reconfigure the ADC to avoid crosstalk between channels (especially on Duet 0.8.5)
adc_init(ADC, SystemCoreClock, ADC_FREQ_MIN, ADC_STARTUP_FAST); // reduce clock rate
@ -240,7 +245,11 @@ void Platform::Init()
{
pinModeNonDue(directionPins[drive], OUTPUT);
}
#ifdef EXTERNAL_DRIVERS
if (drive < FIRST_EXTERNAL_DRIVE && enablePins[drive] >= 0)
#else
if (enablePins[drive] >= 0)
#endif
{
pinModeNonDue(enablePins[drive], OUTPUT);
}
@ -261,6 +270,10 @@ void Platform::Init()
}
}
#ifdef EXTERNAL_DRIVERS
ExternalDrivers::Init();
#endif
extrusionAncilliaryPWM = 0.0;
// HEATERS - Bed is assumed to be index 0
@ -275,14 +288,8 @@ void Platform::Init()
thermistorAdcChannels[heater] = PinToAdcChannel(tempSensePins[heater]); // Translate the Arduino Due Analog pin number to the SAM ADC channel number
SetThermistorNumber(heater, heater); // map the thermistor straight through
thermistorFilters[heater].Init(analogRead(tempSensePins[heater]));
// Calculate and store the ADC average sum that corresponds to an overheat condition, so that we can check is quickly in the tick ISR
float thermistorOverheatResistance = nvData.pidParams[heater].GetRInf()
* exp(-nvData.pidParams[heater].GetBeta() / (BAD_HIGH_TEMPERATURE - ABS_ZERO));
float thermistorOverheatAdcValue = (AD_RANGE_REAL + 1) * thermistorOverheatResistance
/ (thermistorOverheatResistance + nvData.pidParams[heater].thermistorSeriesR);
thermistorOverheatSums[heater] = (uint32_t) (thermistorOverheatAdcValue + 0.9) * THERMISTOR_AVERAGE_READINGS;
}
SetTemperatureLimit(DEFAULT_TEMPERATURE_LIMIT);
InitFans();
@ -338,6 +345,20 @@ void Platform::InvalidateFiles()
}
}
void Platform::SetTemperatureLimit(float t)
{
temperatureLimit = t;
for (size_t heater = 0; heater < HEATERS; heater++)
{
// Calculate and store the ADC average sum that corresponds to an overheat condition, so that we can check it quickly in the tick ISR
float thermistorOverheatResistance = nvData.pidParams[heater].GetRInf()
* exp(-nvData.pidParams[heater].GetBeta() / (temperatureLimit - ABS_ZERO));
float thermistorOverheatAdcValue = (AD_RANGE_REAL + 1) * thermistorOverheatResistance
/ (thermistorOverheatResistance + nvData.pidParams[heater].thermistorSeriesR);
thermistorOverheatSums[heater] = (uint32_t) (thermistorOverheatAdcValue + 0.9) * THERMISTOR_AVERAGE_READINGS;
}
}
// Specify which thermistor channel a particular heater uses
void Platform::SetThermistorNumber(size_t heater, size_t thermistor)
//pre(heater < HEATERS && thermistor < HEATERS)
@ -781,6 +802,7 @@ void Platform::Spin()
OutputBuffer *aux2OutputBuffer = aux2Output->GetFirstItem();
if (aux2OutputBuffer != nullptr)
{
#ifdef SERIAL_AUX2_DEVICE
size_t bytesToWrite = min<size_t>(SERIAL_AUX2_DEVICE.canWrite(), aux2OutputBuffer->BytesLeft());
if (bytesToWrite > 0)
{
@ -792,6 +814,9 @@ void Platform::Spin()
aux2OutputBuffer = OutputBuffer::Release(aux2OutputBuffer);
aux2Output->SetFirstItem(aux2OutputBuffer);
}
#else
aux2OutputBuffer = OutputBuffer::Release(aux2OutputBuffer);
#endif
}
// Write non-blocking data to the USB line
@ -868,7 +893,11 @@ void Platform::SoftwareReset(uint16_t reason)
{
reason |= (uint16_t)SoftwareResetReason::inLwipSpin;
}
if (!SERIAL_AUX_DEVICE.canWrite() || !SERIAL_AUX2_DEVICE.canWrite())
if (!SERIAL_AUX_DEVICE.canWrite()
#ifdef SERIAL_AUX2_DEVICE
|| !SERIAL_AUX2_DEVICE.canWrite()
#endif
)
{
reason |= (uint16_t)SoftwareResetReason::inAuxOutput; // if we are resetting because we are stuck in a Spin function, record whether we are trying to send to aux
}
@ -880,15 +909,6 @@ void Platform::SoftwareReset(uint16_t reason)
temp.magic = SoftwareResetData::magicValue;
temp.resetReason = reason;
GetStackUsage(NULL, NULL, &temp.neverUsedRam);
if (reason != (uint16_t)SoftwareResetReason::user)
{
strncpy(temp.lastMessage, messageString.Pointer(), sizeof(temp.lastMessage) - 1);
temp.lastMessage[sizeof(temp.lastMessage) - 1] = 0;
}
else
{
temp.lastMessage[0] = 0;
}
// Save diagnostics data to Flash and reset the software
DueFlashStorage::write(SoftwareResetData::nvAddress, &temp, sizeof(SoftwareResetData));
@ -1022,10 +1042,6 @@ void Platform::Diagnostics()
{
MessageF(GENERIC_MESSAGE, "Last software reset code & available RAM: 0x%04x, %u\n", temp.resetReason, temp.neverUsedRam);
MessageF(GENERIC_MESSAGE, "Spinning module during software reset: %s\n", moduleName[temp.resetReason & 0x0F]);
if (temp.lastMessage[0])
{
MessageF(GENERIC_MESSAGE, "Last message before reset: %s", temp.lastMessage); // usually ends with NL
}
}
}
@ -1178,7 +1194,10 @@ float Platform::GetTemperature(size_t heater, TempError* err) const
else
{
// thermistor short circuit, return a high temperature
if (err) *err = TempError::errShort;
if (err)
{
*err = TempError::errShort;
}
return BAD_ERROR_TEMPERATURE;
}
}
@ -1216,6 +1235,7 @@ float Platform::GetTemperature(size_t heater, TempError* err) const
case TempError::errShortVcc : return "sensor circuit is shorted to the voltage rail";
case TempError::errShortGnd : return "sensor circuit is shorted to ground";
case TempError::errOpen : return "sensor circuit is open/disconnected";
case TempError::errTooHigh: return "temperature above safety limit";
case TempError::errTimeout : return "communication error whilst reading sensor; read took too long";
case TempError::errIO: return "communication error whilst reading sensor; check sensor connections";
}
@ -1238,6 +1258,7 @@ bool Platform::AnyHeaterHot(uint16_t heaters, float t) const
return false;
}
// Update the heater PID parameters or thermistor resistance etc.
void Platform::SetPidParameters(size_t heater, const PidParameters& params)
{
if (heater < HEATERS && params != nvData.pidParams[heater])
@ -1247,6 +1268,7 @@ void Platform::SetPidParameters(size_t heater, const PidParameters& params)
{
WriteNvData();
}
SetTemperatureLimit(temperatureLimit); // recalculate the thermistor resistance at max allowed temperature for the tick ISR
}
}
const PidParameters& Platform::GetPidParameters(size_t heater) const
@ -1329,11 +1351,22 @@ void Platform::EnableDrive(size_t drive)
{
UpdateMotorCurrent(driver); // the current may have been reduced by the idle timeout
const int pin = enablePins[driver];
if (pin >= 0)
#ifdef EXTERNAL_DRIVERS
if (drive >= FIRST_EXTERNAL_DRIVE)
{
digitalWriteNonDue(pin, enableValues[driver]);
ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, true);
}
else
{
#endif
const int pin = enablePins[driver];
if (pin >= 0)
{
digitalWriteNonDue(pin, enableValues[driver]);
}
#ifdef EXTERNAL_DRIVERS
}
#endif
}
}
}
@ -1344,12 +1377,23 @@ void Platform::DisableDrive(size_t drive)
if (drive < DRIVES)
{
const size_t driver = driverNumbers[drive];
const int pin = enablePins[driver];
if (pin >= 0)
#ifdef EXTERNAL_DRIVERS
if (drive >= FIRST_EXTERNAL_DRIVE)
{
digitalWriteNonDue(pin, !enableValues[driver]);
ExternalDrivers::EnableDrive(driver - FIRST_EXTERNAL_DRIVE, false);
}
driveState[drive] = DriveStatus::disabled;
else
{
#endif
const int pin = enablePins[driver];
if (pin >= 0)
{
digitalWriteNonDue(pin, !enableValues[driver]);
}
driveState[drive] = DriveStatus::disabled;
#ifdef EXTERNAL_DRIVERS
}
#endif
}
}
@ -1387,35 +1431,46 @@ void Platform::UpdateMotorCurrent(size_t drive)
{
current *= idleCurrentFactor;
}
unsigned short pot = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDigipotVoltage/2)/maxStepperDigipotVoltage);
unsigned short dac = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDACVoltage/2)/maxStepperDACVoltage);
const size_t driver = driverNumbers[drive];
if (driver < 4)
#ifdef EXTERNAL_DRIVERS
if (driver >= FIRST_EXTERNAL_DRIVE)
{
mcpDuet.setNonVolatileWiper(potWipes[driver], pot);
mcpDuet.setVolatileWiper(potWipes[driver], pot);
ExternalDrivers::SetCurrent(driver - FIRST_EXTERNAL_DRIVE, current);
}
else
{
if (board == BoardType::Duet_085)
#endif
unsigned short pot = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDigipotVoltage/2)/maxStepperDigipotVoltage);
unsigned short dac = (unsigned short)((0.256*current*8.0*senseResistor + maxStepperDACVoltage/2)/maxStepperDACVoltage);
if (driver < 4)
{
// Extruder 0 is on DAC channel 0
if (driver == 4)
{
analogWrite(DAC0, dac);
}
else
{
mcpExpansion.setNonVolatileWiper(potWipes[driver-1], pot);
mcpExpansion.setVolatileWiper(potWipes[driver-1], pot);
}
mcpDuet.setNonVolatileWiper(potWipes[driver], pot);
mcpDuet.setVolatileWiper(potWipes[driver], pot);
}
else
{
mcpExpansion.setNonVolatileWiper(potWipes[driver], pot);
mcpExpansion.setVolatileWiper(potWipes[driver], pot);
}
if (board == BoardType::Duet_085)
{
// Extruder 0 is on DAC channel 0
if (driver == 4)
{
analogWrite(DAC0, dac);
}
else
{
mcpExpansion.setNonVolatileWiper(potWipes[driver-1], pot);
mcpExpansion.setVolatileWiper(potWipes[driver-1], pot);
}
}
else
{
mcpExpansion.setNonVolatileWiper(potWipes[driver], pot);
mcpExpansion.setVolatileWiper(potWipes[driver], pot);
}
}
#ifdef EXTERNAL_DRIVERS
}
#endif
}
}
@ -1437,6 +1492,48 @@ void Platform::SetIdleCurrentFactor(float f)
}
}
// Set the microstepping, returning true if successful
bool Platform::SetMicrostepping(size_t drive, int microsteps, int mode)
{
if (drive < DRIVES)
{
#ifdef EXTERNAL_DRIVERS
const size_t driver = driverNumbers[drive];
if (driver >= FIRST_EXTERNAL_DRIVE)
{
return ExternalDrivers::SetMicrostepping(driver - FIRST_EXTERNAL_DRIVE, microsteps, mode);
}
else
{
#endif
// On-board drivers only support x16 microstepping.
// We ignore the interpolation on/off parameter so that e.g. M350 I1 E16:128 won't give an error if E1 supports interpolation but E0 doesn't.
return microsteps == 16;
#ifdef EXTERNAL_DRIVERS
}
#endif
}
return false;
}
unsigned int Platform::GetMicrostepping(size_t drive, bool& interpolation) const
{
#ifdef EXTERNAL_DRIVERS
if (drive < DRIVES)
{
const size_t driver = driverNumbers[drive];
if (driver >= FIRST_EXTERNAL_DRIVE)
{
return ExternalDrivers::GetMicrostepping(driver - FIRST_EXTERNAL_DRIVE, interpolation);
}
}
#endif
// On-board drivers only support x16 microstepping without interpolation
interpolation = false;
return 16;
}
// Set the physical drive (i.e. axis or extruder) number used by this driver
void Platform::SetPhysicalDrive(size_t driverNumber, int8_t physicalDrive)
{
@ -1513,7 +1610,7 @@ void Platform::InitFans()
for (size_t i = 0; i < NUM_FANS; ++i)
{
// The cooling fan 0 output pin gets inverted if HEAT_ON == 0 on a Duet 0.4, 0.6 or 0.7
fans[i].Init(COOLING_FAN_PINS[i], !HEAT_ON && board != BoardType::Duet_085);
fans[i].Init(COOLING_FAN_PINS[i], !HEAT_ON && (board == BoardType::Duet_06 || board == BoardType::Duet_07));
}
if (NUM_FANS > 1)
@ -1711,6 +1808,7 @@ void Platform::Message(MessageType type, const char *message)
break;
case AUX2_MESSAGE:
#ifdef SERIAL_AUX2_DEVICE
// Message that is to be sent to the second auxiliary device (blocking)
if (!aux2Output->IsEmpty())
{
@ -1723,6 +1821,7 @@ void Platform::Message(MessageType type, const char *message)
SERIAL_AUX2_DEVICE.write(message);
SERIAL_AUX2_DEVICE.flush();
}
#endif
break;
case DISPLAY_MESSAGE:
@ -1965,10 +2064,12 @@ void Platform::ResetChannel(size_t chan)
SERIAL_AUX_DEVICE.end();
SERIAL_AUX_DEVICE.begin(baudRates[1]);
break;
#ifdef SERIAL_AUX2_DEVICE
case 2:
SERIAL_AUX2_DEVICE.end();
SERIAL_AUX2_DEVICE.begin(baudRates[2]);
break;
#endif
default:
break;
}
@ -1981,7 +2082,7 @@ void Platform::SetBoardType(BoardType bt)
// Determine whether this is a Duet 0.6 or a Duet 0.8.5 board.
// If it is a 0.85 board then DAC0 (AKA digital pin 67) is connected to ground via a diode and a 2.15K resistor.
// So we enable the pullup (value 150K-150K) on pin 67 and read it, expecting a LOW on a 0.8.5 board and a HIGH on a 0.6 board.
// This may fail if anyone connects a load to the DAC0 pin on and Dur=et 0.6, hence we implement board selection in M115 as well.
// This may fail if anyone connects a load to the DAC0 pin on and Duet 0.6, hence we implement board selection in M115 as well.
pinModeNonDue(Dac0DigitalPin, INPUT_PULLUP);
board = (digitalReadNonDue(Dac0DigitalPin)) ? BoardType::Duet_06 : BoardType::Duet_085;
pinModeNonDue(Dac0DigitalPin, INPUT); // turn pullup off
@ -2088,7 +2189,11 @@ bool Platform::GCodeAvailable(const SerialSource source) const
return SERIAL_AUX_DEVICE.available() > 0;
case SerialSource::AUX2:
#ifdef SERIAL_AUX2_DEVICE
return SERIAL_AUX2_DEVICE.available() > 0;
#else
return false;
#endif
}
return false;
@ -2105,7 +2210,11 @@ char Platform::ReadFromSource(const SerialSource source)
return static_cast<char>(SERIAL_AUX_DEVICE.read());
case SerialSource::AUX2:
#ifdef SERIAL_AUX2_DEVICE
return static_cast<char>(SERIAL_AUX2_DEVICE.read());
#else
return 0;
#endif
}
return 0;
@ -2180,7 +2289,7 @@ void Platform::Tick()
// averaging. As such, the temperature reading is taken directly by Platform::GetTemperature() and
// periodically called by PID::Spin() where temperature fault handling is taken care of. However, we
// must guard against overly long delays between successive calls of PID::Spin().
// Do not call Time() here, it isn't safe. We use millis() instead.
StartAdcConversion(zProbeAdcChannel);
if ((millis() - reprap.GetHeat()->GetLastSampleTime(currentHeater)) > maxPidSpinDelay)
{

View file

@ -175,12 +175,6 @@ const uint8_t MAC_ADDRESS[6] = { 0xBE, 0xEF, 0xDE, 0xAD, 0xFE, 0xED };
/****************************************************************************************************/
// Miscellaneous...
const size_t messageStringLength = 256; // max length of a message chunk sent via Message or AppendMessage
/****************************************************************************************************/
enum class BoardType : uint8_t
{
Auto = 0,
@ -423,7 +417,7 @@ public:
enum class DriveStatus : uint8_t { disabled, idle, enabled };
// Error results generated by GetTemperature()
enum class TempError : uint8_t { errOk, errShort, errShortVcc, errShortGnd, errOpen, errTimeout, errIO };
enum class TempError : uint8_t { errOk, errShort, errShortVcc, errShortGnd, errOpen, errTooHigh, errTimeout, errIO };
Platform();
@ -515,6 +509,8 @@ public:
float MotorCurrent(size_t drive) const;
void SetIdleCurrentFactor(float f);
float GetIdleCurrentFactor() const { return idleCurrentFactor; }
bool SetMicrostepping(size_t drive, int microsteps, int mode);
unsigned int GetMicrostepping(size_t drive, bool& interpolation) const;
float DriveStepsPerUnit(size_t drive) const;
const float *GetDriveStepsPerUnit() const { return driveStepsPerUnit; }
void SetDriveStepsPerUnit(size_t drive, float value);
@ -574,7 +570,8 @@ public:
void SetThermistorNumber(size_t heater, size_t thermistor);
int GetThermistorNumber(size_t heater) const;
bool DoThermistorAdc(uint8_t heater) const;
MAX31855 Max31855Devices[MAX31855_DEVICES];
void SetTemperatureLimit(float t);
float GetTemperatureLimit() const { return temperatureLimit; }
static const char* TempErrorStr(TempError err);
static bool TempErrorPermanent(TempError err);
@ -638,7 +635,6 @@ private:
uint16_t magic;
uint16_t resetReason; // this records why we did a software reset, for diagnostic purposes
size_t neverUsedRam; // the amount of never used RAM at the last abnormal software reset
char lastMessage[256]; // the last known message before a software reset occurred
};
struct FlashData
@ -759,11 +755,13 @@ private:
Pin tempSensePins[HEATERS];
Pin heatOnPins[HEATERS];
MAX31855 Max31855Devices[MAX31855_DEVICES];
Pin max31855CsPins[MAX31855_DEVICES];
float heatSampleTime;
float standbyTemperatures[HEATERS];
float activeTemperatures[HEATERS];
float timeToHot;
float temperatureLimit;
// Fans
@ -836,9 +834,6 @@ private:
static uint16_t GetAdcReading(adc_channel_num_t chan);
static void StartAdcConversion(adc_channel_num_t chan);
char messageStringBuffer[messageStringLength];
StringRef messageString;
// Hotend configuration
float filamentWidth;
float nozzleDiameter;

View file

@ -723,23 +723,27 @@ float PrintMonitor::EstimateTimeLeft(PrintEstimationMethod method) const
const float fractionPrinted = gCodes->FractionOfFilePrinted();
if (numLayerSamples < 2 || !printingFileParsed || printingFileInfo.objectHeight == 0.0)
{
return (fractionPrinted < 0.01)
? 0.0
: realPrintDuration * (1.0 / fractionPrinted) - realPrintDuration;
if (fractionPrinted < 0.01)
{
break;
}
return realPrintDuration * (1.0 / fractionPrinted) - realPrintDuration;
}
// Work out how much progress we made in the layers we have data for, and how long it took.
// Can't use the first layer sample in the table because we don't know the fraction printed at the start.
float duration = 0.0;
for(size_t layer = 1; layer < numLayerSamples; layer++)
for (size_t layer = 1; layer < numLayerSamples; layer++)
{
duration += layerDurations[layer];
}
const float fractionPrintedInLayers = fileProgressPerLayer[numLayerSamples - 1] - fileProgressPerLayer[0];
return (fractionPrintedInLayers < 0.01)
? 0.0
: duration * (1.0 - fractionPrinted)/fractionPrintedInLayers;
if (fractionPrintedInLayers >= 0.01)
{
return duration * (1.0 - fractionPrinted)/fractionPrintedInLayers;
}
}
break;
case filamentBased:
{
@ -750,7 +754,7 @@ float PrintMonitor::EstimateTimeLeft(PrintEstimationMethod method) const
#endif
)
{
return 0.0;
break;
}
// Sum up the filament usage and the filament needed
@ -770,22 +774,35 @@ float PrintMonitor::EstimateTimeLeft(PrintEstimationMethod method) const
}
if (extrRawTotal >= totalFilamentNeeded)
{
return 0.0; // Avoid division by zero, else the web interface will report AJAX errors
break; // Avoid division by zero, else the web interface will report AJAX errors
}
float filamentRate;
if (numLayerSamples)
if (numLayerSamples != 0)
{
filamentRate = 0.0;
for(size_t i = 0; i < numLayerSamples; i++)
size_t numSamples = 0;
for (size_t i = 0; i < numLayerSamples; i++)
{
filamentRate += filamentUsagePerLayer[i] / layerDurations[i];
if (layerDurations[i] > 0.0)
{
filamentRate += filamentUsagePerLayer[i] / layerDurations[i];
++numSamples;
}
}
filamentRate /= numLayerSamples;
if (numSamples == 0)
{
break;
}
filamentRate /= numSamples;
}
else if (firstLayerDuration > 0.0)
{
filamentRate = firstLayerFilament / firstLayerDuration;
}
else
{
filamentRate = firstLayerFilament / firstLayerDuration;
break;
}
return (totalFilamentNeeded - extrRawTotal) / filamentRate;

Binary file not shown.

View file

@ -163,7 +163,7 @@ void RepRap::Spin()
{
if (t->DisplayColdExtrudeWarning() && ToolWarningsAllowed())
{
platform->MessageF(GENERIC_MESSAGE, "Warning: Tool %d was not driven because its heater temperatures were not high enough\n", t->myNumber);
platform->MessageF(GENERIC_MESSAGE, "Warning: Tool %d was not driven because its temperatures were not high enough or it has a heater fault\n", t->myNumber);
}
}
@ -1341,37 +1341,44 @@ void RepRap::SetName(const char* nm)
network->SetHostname(myName);
}
// Given that we want to extrude/etract the specified extruder drives, check if they are allowed.
// Given that we want to extrude/retract the specified extruder drives, check if they are allowed.
// For each disallowed one, log an error to report later and return a bit in the bitmap.
// This may be called by an ISR!
unsigned int RepRap::GetProhibitedExtruderMovements(unsigned int extrusions, unsigned int retractions)
{
unsigned int result = 0;
Tool *tool = toolList;
while (tool != nullptr)
if (GetHeat()->ColdExtrude())
{
for (size_t driveNum = 0; driveNum < tool->DriveCount(); driveNum++)
return 0;
}
Tool *tool = currentTool;
if (tool == nullptr)
{
// This should not happen, but if on tool is selected then don't allow any extruder movement
return extrusions | retractions;
}
unsigned int result = 0;
for (size_t driveNum = 0; driveNum < tool->DriveCount(); driveNum++)
{
const unsigned int extruderDrive = (unsigned int)(tool->Drive(driveNum));
const unsigned int mask = 1 << extruderDrive;
if (extrusions & mask)
{
const int extruderDrive = tool->Drive(driveNum);
unsigned int mask = 1 << extruderDrive;
if (extrusions & mask)
if (!tool->ToolCanDrive(true))
{
if (!tool->ToolCanDrive(true))
{
result |= mask;
}
}
else if (retractions & (1 << extruderDrive))
{
if (!tool->ToolCanDrive(false))
{
result |= mask;
}
result |= mask;
}
}
else if (retractions & mask)
{
if (!tool->ToolCanDrive(false))
{
result |= mask;
}
}
tool = tool->Next();
}
return result;
}

View file

@ -1,43 +1,45 @@
/****************************************************************************************************
RepRapFirmware - Tool
RepRapFirmware - Tool
This class implements a tool in the RepRap machine, usually (though not necessarily) an extruder.
This class implements a tool in the RepRap machine, usually (though not necessarily) an extruder.
Tools may have zero or more drives associated with them and zero or more heaters. There are a fixed number
of tools in a given RepRap, with fixed heaters and drives. All this is specified on reboot, and cannot
be altered dynamically. This restriction may be lifted in the future. Tool descriptions are stored in
GCode macros that are loaded on reboot.
Tools may have zero or more drives associated with them and zero or more heaters. There are a fixed number
of tools in a given RepRap, with fixed heaters and drives. All this is specified on reboot, and cannot
be altered dynamically. This restriction may be lifted in the future. Tool descriptions are stored in
GCode macros that are loaded on reboot.
-----------------------------------------------------------------------------------------------------
-----------------------------------------------------------------------------------------------------
Version 0.1
Version 0.1
Created on: Apr 11, 2014
Created on: Apr 11, 2014
Adrian Bowyer
RepRap Professional Ltd
http://reprappro.com
Adrian Bowyer
RepRap Professional Ltd
http://reprappro.com
Licence: GPL
Licence: GPL
****************************************************************************************************/
****************************************************************************************************/
#include "RepRapFirmware.h"
Tool * Tool::freelist = nullptr;
/*static*/ Tool * Tool::Create(int toolNumber, long d[], size_t dCount, long h[], size_t hCount)
/*static*/Tool * Tool::Create(int toolNumber, long d[], size_t dCount, long h[], size_t hCount)
{
if (dCount > DRIVES - AXES)
{
reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Error: Tool creation: attempt to use more drives than there are in the RepRap");
reprap.GetPlatform()->Message(GENERIC_MESSAGE,
"Error: Tool creation: attempt to use more drives than there are in the RepRap");
return nullptr;
}
if (hCount > HEATERS)
{
reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Error: Tool creation: attempt to use more heaters than there are in the RepRap");
reprap.GetPlatform()->Message(GENERIC_MESSAGE,
"Error: Tool creation: attempt to use more heaters than there are in the RepRap");
return nullptr;
}
@ -61,14 +63,14 @@ Tool * Tool::freelist = nullptr;
t->mixing = false;
t->displayColdExtrudeWarning = false;
for(size_t axis = 0; axis < AXES; axis++)
for (size_t axis = 0; axis < AXES; axis++)
{
t->offset[axis] = 0.0;
}
if (t->driveCount > 0)
{
float r = 1.0/(float)(t->driveCount);
float r = 1.0 / (float) (t->driveCount);
for (size_t drive = 0; drive < t->driveCount; drive++)
{
@ -79,7 +81,7 @@ Tool * Tool::freelist = nullptr;
if (t->heaterCount > 0)
{
for(size_t heater = 0; heater < t->heaterCount; heater++)
for (size_t heater = 0; heater < t->heaterCount; heater++)
{
t->heaters[heater] = h[heater];
t->activeTemperatures[heater] = ABS_ZERO;
@ -90,7 +92,7 @@ Tool * Tool::freelist = nullptr;
return t;
}
/*static*/ void Tool::Delete(Tool *t)
/*static*/void Tool::Delete(Tool *t)
{
if (t != nullptr)
{
@ -116,12 +118,11 @@ void Tool::Print(StringRef& reply)
comma = ',';
for (size_t heater = 0; heater < heaterCount; heater++)
{
if (heater >= heaterCount - 1)
{
comma = ';';
}
reply.catf("%d (%.1f/%.1f)%c", heaters[heater],
activeTemperatures[heater], standbyTemperatures[heater], comma);
if (heater >= heaterCount - 1)
{
comma = ';';
}
reply.catf("%d (%.1f/%.1f)%c", heaters[heater], activeTemperatures[heater], standbyTemperatures[heater], comma);
}
reply.catf(" status: %s", active ? "selected" : "standby");
@ -131,7 +132,8 @@ float Tool::MaxFeedrate() const
{
if (driveCount <= 0)
{
reprap.GetPlatform()->Message(GENERIC_MESSAGE, "Error: Attempt to get maximum feedrate for a tool with no drives.\n");
reprap.GetPlatform()->Message(GENERIC_MESSAGE,
"Error: Attempt to get maximum feedrate for a tool with no drives.\n");
return 1.0;
}
float result = 0.0;
@ -173,7 +175,7 @@ float Tool::InstantDv() const
void Tool::FlagTemperatureFault(int8_t heater)
{
Tool* n = this;
while(n != nullptr)
while (n != nullptr)
{
n->SetTemperatureFault(heater);
n = n->Next();
@ -183,7 +185,7 @@ void Tool::FlagTemperatureFault(int8_t heater)
void Tool::ClearTemperatureFault(int8_t heater)
{
Tool* n = this;
while(n != nullptr)
while (n != nullptr)
{
n->ResetTemperatureFault(heater);
n = n->Next();
@ -194,7 +196,7 @@ void Tool::SetTemperatureFault(int8_t dudHeater)
{
for (size_t heater = 0; heater < heaterCount; heater++)
{
if(dudHeater == heaters[heater])
if (dudHeater == heaters[heater])
{
heaterFault = true;
return;
@ -269,12 +271,12 @@ void Tool::SetVariables(const float* standby, const float* active)
}
else
{
if (active[heater] < BAD_HIGH_TEMPERATURE)
if (active[heater] < reprap.GetPlatform()->GetTemperatureLimit())
{
activeTemperatures[heater] = active[heater];
reprap.GetHeat()->SetActiveTemperature(heaters[heater], activeTemperatures[heater]);
}
if (standby[heater] < BAD_HIGH_TEMPERATURE)
if (standby[heater] < reprap.GetPlatform()->GetTemperatureLimit())
{
standbyTemperatures[heater] = standby[heater];
reprap.GetHeat()->SetStandbyTemperature(heaters[heater], standbyTemperatures[heater]);
@ -295,11 +297,10 @@ void Tool::GetVariables(float* standby, float* active) const
// May be called from ISR
bool Tool::ToolCanDrive(bool extrude)
{
if (heaterFault)
return false;
if (reprap.GetHeat()->ColdExtrude() || AllHeatersAtHighTemperature(extrude))
if (!heaterFault && AllHeatersAtHighTemperature(extrude))
{
return true;
}
displayColdExtrudeWarning = true;
return false;

View file

@ -96,8 +96,8 @@
//***************************************************************************************************
static const char* overflowResponse = "overflow";
static const char* badEscapeResponse = "bad escape";
const char* overflowResponse = "overflow";
const char* badEscapeResponse = "bad escape";
//********************************************************************************************