
Merged in Dan's thermocouple support Define serial port allocation in Pins_duet.h Fixed some leaks of OutputBuffer objects under error conditions The terminating newline character in a gcode response is now added in HandleReply
307 lines
8.9 KiB
C++
307 lines
8.9 KiB
C++
/****************************************************************************************************
|
|
|
|
RepRapFirmware - Heat
|
|
|
|
This is all the code to deal with heat and temperature.
|
|
|
|
-----------------------------------------------------------------------------------------------------
|
|
|
|
Version 0.1
|
|
|
|
18 November 2012
|
|
|
|
Adrian Bowyer
|
|
RepRap Professional Ltd
|
|
http://reprappro.com
|
|
|
|
Licence: GPL
|
|
|
|
****************************************************************************************************/
|
|
|
|
#include "RepRapFirmware.h"
|
|
|
|
const float invHeatPwmAverageCount = HEAT_SAMPLE_TIME/HEAT_PWM_AVERAGE_TIME;
|
|
|
|
Heat::Heat(Platform* p) : platform(p), active(false), coldExtrude(false),
|
|
bedHeater(BED_HEATER), chamberHeater(-1)
|
|
{
|
|
for (size_t heater = 0; heater < HEATERS; heater++)
|
|
{
|
|
pids[heater] = new PID(platform, heater);
|
|
}
|
|
}
|
|
|
|
void Heat::Init()
|
|
{
|
|
for (size_t heater = 0; heater < HEATERS; heater++)
|
|
{
|
|
pids[heater]->Init();
|
|
}
|
|
lastTime = platform->Time();
|
|
longWait = lastTime;
|
|
coldExtrude = false;
|
|
active = true;
|
|
}
|
|
|
|
void Heat::Exit()
|
|
{
|
|
for (size_t heater = 0; heater < HEATERS; heater++)
|
|
{
|
|
pids[heater]->SwitchOff();
|
|
}
|
|
platform->Message(HOST_MESSAGE, "Heat class exited.\n");
|
|
active = false;
|
|
}
|
|
|
|
void Heat::Spin()
|
|
{
|
|
if (active)
|
|
{
|
|
float t = platform->Time();
|
|
if (t - lastTime >= platform->HeatSampleTime())
|
|
{
|
|
lastTime = t;
|
|
for (size_t heater=0; heater < HEATERS; heater++)
|
|
{
|
|
pids[heater]->Spin();
|
|
}
|
|
platform->ClassReport(longWait);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Heat::Diagnostics()
|
|
{
|
|
platform->Message(GENERIC_MESSAGE, "Heat Diagnostics:\n");
|
|
for (size_t heater=0; heater < HEATERS; heater++)
|
|
{
|
|
if (pids[heater]->active)
|
|
{
|
|
platform->MessageF(GENERIC_MESSAGE, "Heater %d: I-accumulator = %.1f\n", heater, pids[heater]->temp_iState);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Heat::AllHeatersAtSetTemperatures(bool includingBed) const
|
|
{
|
|
for (int8_t heater = (includingBed) ? 0 : 1; heater < HEATERS; heater++)
|
|
{
|
|
if (!HeaterAtSetTemperature(heater))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//query an individual heater
|
|
bool Heat::HeaterAtSetTemperature(int8_t heater) const
|
|
{
|
|
if (pids[heater]->SwitchedOff()) // If it hasn't anything to do, it must be right wherever it is...
|
|
return true;
|
|
|
|
float dt = GetTemperature(heater);
|
|
float target = (pids[heater]->Active()) ? GetActiveTemperature(heater) : GetStandbyTemperature(heater);
|
|
return (target < TEMPERATURE_LOW_SO_DONT_CARE) || (fabs(dt - target) <= TEMPERATURE_CLOSE_ENOUGH);
|
|
}
|
|
|
|
//******************************************************************************************************
|
|
|
|
PID::PID(Platform* p, int8_t h) : platform(p), heater(h)
|
|
{
|
|
}
|
|
|
|
void PID::Init()
|
|
{
|
|
SetHeater(0.0);
|
|
temperature = platform->GetTemperature(heater);
|
|
activeTemperature = ABS_ZERO;
|
|
standbyTemperature = ABS_ZERO;
|
|
lastTemperature = temperature;
|
|
temp_iState = 0.0;
|
|
badTemperatureCount = 0;
|
|
temperatureFault = false;
|
|
active = false; // Default to standby temperature
|
|
switchedOff = true;
|
|
heatingUp = false;
|
|
averagePWM = 0.0;
|
|
|
|
// Time the sensor was last sampled. During startup, we use the current
|
|
// time as the initial value so as to not trigger an immediate warning from
|
|
// the Tick ISR.
|
|
lastSampleTime = platform->Time();
|
|
}
|
|
|
|
void PID::SwitchOn()
|
|
{
|
|
// if(reprap.Debug())
|
|
// {
|
|
// snprintf(scratchString, STRING_LENGTH, "Heater %d switched on.\n", heater);
|
|
// platform->Message(BOTH_MESSAGE, scratchString);
|
|
// }
|
|
switchedOff = false;
|
|
}
|
|
|
|
void PID::SetHeater(float power) const
|
|
{
|
|
platform->SetHeater(heater, power);
|
|
}
|
|
|
|
void PID::Spin()
|
|
{
|
|
// For temperature sensors which do not require frequent sampling and averaging,
|
|
// their temperature is read here and error/safety handling performed. However,
|
|
// unlike the Tick ISR, this code is not executed at interrupt level and consequently
|
|
// runs the risk of having undesirable delays between calls. To guard against this,
|
|
// we record for each PID object when it was last sampled and have the Tick ISR
|
|
// take action if there is a significant delay since the time of last sampling.
|
|
lastSampleTime = platform->Time();
|
|
|
|
// 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
|
|
|
|
// 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
|
|
// we don't even have a thermistor connected. So don't even check for faults if we
|
|
// are not switched on. This is safe, as the next bit of code always turns our
|
|
// heater off in that case anyway.
|
|
if (temperatureFault || switchedOff)
|
|
{
|
|
SetHeater(0.0); // Make sure...
|
|
averagePWM *= (1.0 - invHeatPwmAverageCount);
|
|
return;
|
|
}
|
|
|
|
// 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 (platform->DoThermistorAdc(heater) || !(Platform::TempErrorPermanent(err)))
|
|
{
|
|
// Error may be a temporary error and may correct itself after a few additional reads
|
|
badTemperatureCount++;
|
|
}
|
|
else
|
|
{
|
|
// Error condition is not expected to correct itself (e.g., digital device such
|
|
// as a MAX31855 reporting that the temp sensor is shorted -- even a temporary
|
|
// short in such a situation warrants manual attention and correction).
|
|
badTemperatureCount = MAX_BAD_TEMPERATURE_COUNT + 1;
|
|
}
|
|
|
|
if (badTemperatureCount > MAX_BAD_TEMPERATURE_COUNT)
|
|
{
|
|
SetHeater(0.0);
|
|
temperatureFault = true;
|
|
//switchedOff = true;
|
|
platform->MessageF(GENERIC_MESSAGE, "Temperature fault on heater %d%s%s, T = %.1f\n",
|
|
heater,
|
|
(err != Platform::TempError::errOk) ? ", " : "",
|
|
(err != Platform::TempError::errOk) ? Platform::TempErrorStr(err) : "",
|
|
temperature);
|
|
reprap.FlagTemperatureFault(heater);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
badTemperatureCount = 0;
|
|
}
|
|
|
|
// Now check how long it takes to warm up. If too long, maybe the thermistor is not in contact with the heater
|
|
if (heatingUp && heater != BED_HEATER) // FIXME - also check bed warmup time?
|
|
{
|
|
float tmp = (active) ? activeTemperature : standbyTemperature;
|
|
if (temperature < tmp - TEMPERATURE_CLOSE_ENOUGH)
|
|
{
|
|
float tim = platform->Time() - timeSetHeating;
|
|
float limit = platform->TimeToHot();
|
|
if (tim > limit && limit > 0.0)
|
|
{
|
|
SetHeater(0.0);
|
|
temperatureFault = true;
|
|
//switchedOff = true;
|
|
platform->MessageF(GENERIC_MESSAGE, "Heating fault on heater %d, T = %.1f C; still not at temperature %.1f after %f seconds.\n", heater, temperature, tmp, tim);
|
|
reprap.FlagTemperatureFault(heater);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
heatingUp = false;
|
|
}
|
|
}
|
|
|
|
float targetTemperature = (active) ? activeTemperature : standbyTemperature;
|
|
float error = targetTemperature - temperature;
|
|
const PidParameters& pp = platform->GetPidParameters(heater);
|
|
|
|
if (!pp.UsePID())
|
|
{
|
|
float heaterValue = (error > 0.0) ? min<float>(pp.kS, 1.0) : 0.0;
|
|
SetHeater(heaterValue);
|
|
averagePWM = averagePWM * (1.0 - invHeatPwmAverageCount) + heaterValue;
|
|
return;
|
|
}
|
|
|
|
if(error < -pp.fullBand)
|
|
{
|
|
// actual temperature is well above target
|
|
temp_iState = (targetTemperature + pp.fullBand - 25.0) * pp.kT; // set the I term to our estimate of what will be needed ready for the switch to PID
|
|
SetHeater(0.0);
|
|
averagePWM *= (1.0 - invHeatPwmAverageCount);
|
|
lastTemperature = temperature;
|
|
return;
|
|
}
|
|
if(error > pp.fullBand)
|
|
{
|
|
// actual temperature is well below target
|
|
temp_iState = (targetTemperature - pp.fullBand - 25.0) * pp.kT; // set the I term to our estimate of what will be needed ready for the switch to PID
|
|
float heaterValue = min<float>(pp.kS, 1.0);
|
|
SetHeater(heaterValue);
|
|
averagePWM = averagePWM * (1.0 - invHeatPwmAverageCount) + heaterValue;
|
|
lastTemperature = temperature;
|
|
return;
|
|
}
|
|
|
|
float sampleInterval = platform->HeatSampleTime();
|
|
temp_iState += error * pp.kI * sampleInterval;
|
|
|
|
if (temp_iState < pp.pidMin)
|
|
{
|
|
temp_iState = pp.pidMin;
|
|
}
|
|
else if (temp_iState > pp.pidMax)
|
|
{
|
|
temp_iState = pp.pidMax;
|
|
}
|
|
|
|
float temp_dState = pp.kD * (temperature - lastTemperature) / sampleInterval;
|
|
float result = (pp.kP * error + temp_iState - temp_dState) * pp.kS / 255.0;
|
|
|
|
lastTemperature = temperature;
|
|
|
|
if (result < 0.0)
|
|
{
|
|
result = 0.0;
|
|
}
|
|
else if (result > 1.0)
|
|
{
|
|
result = 1.0;
|
|
}
|
|
|
|
if (!temperatureFault)
|
|
{
|
|
SetHeater(result);
|
|
}
|
|
|
|
averagePWM = averagePWM * (1.0 - invHeatPwmAverageCount) + result;
|
|
|
|
// debugPrintf("Heater %d: e=%f, P=%f, I=%f, d=%f, r=%f\n", heater, error, pp.kP*error, temp_iState, temp_dState, result);
|
|
}
|
|
|
|
float PID::GetAveragePWM() const
|
|
{
|
|
return averagePWM * invHeatPwmAverageCount;
|
|
}
|
|
|
|
// End
|
|
|