preliminary heater sanity check code, disabled by default until tested
This commit is contained in:
parent
4298f7553f
commit
2b1fbe38db
|
|
@ -182,19 +182,21 @@
|
||||||
\***************************************************************************/
|
\***************************************************************************/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TEMP_HYSTERESIS: actual temperature must be target +/- hysteresis before target temperature can be achieved
|
TEMP_HYSTERESIS: actual temperature must be target +/- hysteresis before target temperature can be achieved.
|
||||||
|
NOTE: format is 30.2 fixed point, so value of 20 actually means +/- 5 degrees
|
||||||
|
|
||||||
TEMP_RESIDENCY_TIME: actual temperature must be close to target for this long before target is achieved
|
TEMP_RESIDENCY_TIME: actual temperature must be close to target for this long before target is achieved
|
||||||
|
|
||||||
temperature is "achieved" for purposes of M109 and friends when actual temperature is within [hysteresis] of target for [residency] seconds
|
temperature is "achieved" for purposes of M109 and friends when actual temperature is within [hysteresis] of target for [residency] seconds
|
||||||
*/
|
*/
|
||||||
#define TEMP_HYSTERESIS 5
|
#define TEMP_HYSTERESIS 20
|
||||||
#define TEMP_RESIDENCY_TIME 60
|
#define TEMP_RESIDENCY_TIME 60
|
||||||
|
|
||||||
// which temperature sensors are you using? (intercom is the gen3-style separate extruder board)
|
// which temperature sensors are you using? (intercom is the gen3-style separate extruder board)
|
||||||
// #define TEMP_MAX6675
|
// #define TEMP_MAX6675
|
||||||
// #define TEMP_THERMISTOR
|
// #define TEMP_THERMISTOR
|
||||||
// #define TEMP_AD595
|
// #define TEMP_AD595
|
||||||
// #define TEMP_INTERCOM
|
#define TEMP_INTERCOM
|
||||||
|
|
||||||
// if you selected thermistor or AD595, what pin is it on? (this value only used to fill ANALOG_MASK for you)
|
// if you selected thermistor or AD595, what pin is it on? (this value only used to fill ANALOG_MASK for you)
|
||||||
#define TEMP_PIN_CHANNEL AIO0_PIN
|
#define TEMP_PIN_CHANNEL AIO0_PIN
|
||||||
|
|
@ -245,6 +247,9 @@ struct {
|
||||||
// number of heaters- for GEN3, set to zero as extruder manages the heater by itself
|
// number of heaters- for GEN3, set to zero as extruder manages the heater by itself
|
||||||
#define NUM_HEATERS 0
|
#define NUM_HEATERS 0
|
||||||
|
|
||||||
|
// check if heater responds to changes in target temperature, disable and spit errors if not
|
||||||
|
// #define HEATER_SANITY_CHECK
|
||||||
|
|
||||||
/***************************************************************************\
|
/***************************************************************************\
|
||||||
* *
|
* *
|
||||||
* Fill in the following struct according to your hardware *
|
* Fill in the following struct according to your hardware *
|
||||||
|
|
|
||||||
110
heater.c
110
heater.c
|
|
@ -1,14 +1,12 @@
|
||||||
#include "heater.h"
|
#include "heater.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
#include <avr/eeprom.h>
|
#include <avr/eeprom.h>
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
|
|
||||||
#include "arduino.h"
|
#include "arduino.h"
|
||||||
// #include "timer.h"
|
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
#ifdef DEBUG
|
#include "sersendf.h"
|
||||||
#include "sersendf.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define HEATER_C
|
#define HEATER_C
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
@ -21,21 +19,22 @@ struct {
|
||||||
int16_t i_limit;
|
int16_t i_limit;
|
||||||
} heaters_pid[NUM_HEATERS];
|
} heaters_pid[NUM_HEATERS];
|
||||||
|
|
||||||
// this struct holds the runtime heater data- PID counters and such
|
// this struct holds the runtime heater data- PID integrator history, temperature history, sanity checker
|
||||||
struct {
|
struct {
|
||||||
int16_t heater_p;
|
|
||||||
int16_t heater_i;
|
int16_t heater_i;
|
||||||
int16_t heater_d;
|
|
||||||
|
|
||||||
uint8_t pid_output;
|
|
||||||
|
|
||||||
uint16_t temp_history[TH_COUNT];
|
uint16_t temp_history[TH_COUNT];
|
||||||
uint8_t temp_history_pointer;
|
uint8_t temp_history_pointer;
|
||||||
|
|
||||||
|
#ifdef HEATER_SANITY_CHECK
|
||||||
|
uint16_t sanity_counter;
|
||||||
|
uint16_t sane_temperature;
|
||||||
|
#endif
|
||||||
} heaters_runtime[NUM_HEATERS];
|
} heaters_runtime[NUM_HEATERS];
|
||||||
|
|
||||||
#define DEFAULT_P 8192
|
#define DEFAULT_P 8192
|
||||||
#define DEFAULT_I 512
|
#define DEFAULT_I 512
|
||||||
#define DEFAULT_D -24576
|
#define DEFAULT_D 24576
|
||||||
#define DEFAULT_I_LIMIT 384
|
#define DEFAULT_I_LIMIT 384
|
||||||
|
|
||||||
// this lives in the eeprom so we can save our PID settings for each heater
|
// this lives in the eeprom so we can save our PID settings for each heater
|
||||||
|
|
@ -55,9 +54,10 @@ void heater_init() {
|
||||||
for (i = 0; i < NUM_HEATERS; i++) {
|
for (i = 0; i < NUM_HEATERS; i++) {
|
||||||
*(heaters[i].heater_port) &= ~MASK(heaters[i].heater_pin);
|
*(heaters[i].heater_port) &= ~MASK(heaters[i].heater_pin);
|
||||||
// DDR is always 1 address below PORT. ugly code but saves ram and an extra field in heaters[] which will never be used anywhere but here
|
// DDR is always 1 address below PORT. ugly code but saves ram and an extra field in heaters[] which will never be used anywhere but here
|
||||||
*((volatile uint8_t *) (heaters[i].heater_port - 1)) |= MASK(heaters[i].heater_pin);
|
*(heaters[i].heater_port - 1) |= MASK(heaters[i].heater_pin);
|
||||||
if (heaters[i].heater_pwm) {
|
if (heaters[i].heater_pwm) {
|
||||||
*heaters[i].heater_pwm = 0;
|
*heaters[i].heater_pwm = 0;
|
||||||
|
// this is somewhat ugly too, but switch() won't accept pointers for reasons unknown
|
||||||
switch((uint16_t) heaters[i].heater_pwm) {
|
switch((uint16_t) heaters[i].heater_pwm) {
|
||||||
case (uint16_t) &OCR0A:
|
case (uint16_t) &OCR0A:
|
||||||
TCCR0A |= MASK(COM0A1);
|
TCCR0A |= MASK(COM0A1);
|
||||||
|
|
@ -73,10 +73,11 @@ void heater_init() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// 0 is a "sane" temperature when we're trying to cool down
|
||||||
|
heaters_runtime[i].sane_temperature = 0;
|
||||||
|
|
||||||
// read factors from eeprom
|
// read factors from eeprom
|
||||||
for (i = 0; i < NUM_HEATERS; i++) {
|
|
||||||
heaters_pid[i].p_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_p_factor);
|
heaters_pid[i].p_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_p_factor);
|
||||||
heaters_pid[i].i_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_i_factor);
|
heaters_pid[i].i_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_i_factor);
|
||||||
heaters_pid[i].d_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_d_factor);
|
heaters_pid[i].d_factor = eeprom_read_dword((uint32_t *) &EE_factors[i].EE_d_factor);
|
||||||
|
|
@ -103,15 +104,18 @@ void heater_save_settings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void heater_tick(uint8_t h, uint16_t current_temp, uint16_t target_temp) {
|
void heater_tick(uint8_t h, uint16_t current_temp, uint16_t target_temp) {
|
||||||
// now for heater stuff
|
int16_t heater_p;
|
||||||
int16_t t_error = target_temp - current_temp;
|
int16_t heater_d;
|
||||||
|
uint8_t pid_output;
|
||||||
|
|
||||||
|
int16_t t_error = target_temp - current_temp;
|
||||||
|
|
||||||
heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer++] = current_temp;
|
heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer++] = current_temp;
|
||||||
heaters_runtime[h].temp_history_pointer &= (TH_COUNT - 1);
|
heaters_runtime[h].temp_history_pointer &= (TH_COUNT - 1);
|
||||||
|
|
||||||
// PID stuff
|
// PID stuff
|
||||||
// proportional
|
// proportional
|
||||||
heaters_runtime[h].heater_p = t_error;
|
heater_p = t_error;
|
||||||
|
|
||||||
// integral
|
// integral
|
||||||
heaters_runtime[h].heater_i += t_error;
|
heaters_runtime[h].heater_i += t_error;
|
||||||
|
|
@ -123,31 +127,87 @@ void heater_tick(uint8_t h, uint16_t current_temp, uint16_t target_temp) {
|
||||||
|
|
||||||
// derivative
|
// derivative
|
||||||
// note: D follows temp rather than error so there's no large derivative when the target changes
|
// note: D follows temp rather than error so there's no large derivative when the target changes
|
||||||
heaters_runtime[h].heater_d = current_temp - heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer];
|
heater_d = heaters_runtime[h].temp_history[heaters_runtime[h].temp_history_pointer] - current_temp;
|
||||||
|
|
||||||
// combine factors
|
// combine factors
|
||||||
int32_t pid_output_intermed = (
|
int32_t pid_output_intermed = (
|
||||||
(
|
(
|
||||||
(((int32_t) heaters_runtime[h].heater_p) * heaters_pid[h].p_factor) +
|
(((int32_t) heater_p) * heaters_pid[h].p_factor) +
|
||||||
(((int32_t) heaters_runtime[h].heater_i) * heaters_pid[h].i_factor) +
|
(((int32_t) heaters_runtime[h].heater_i) * heaters_pid[h].i_factor) +
|
||||||
(((int32_t) heaters_runtime[h].heater_d) * heaters_pid[h].d_factor)
|
(((int32_t) heater_d) * heaters_pid[h].d_factor)
|
||||||
) / PID_SCALE
|
) / PID_SCALE
|
||||||
);
|
);
|
||||||
|
|
||||||
// rebase and limit factors
|
// rebase and limit factors
|
||||||
if (pid_output_intermed > 255)
|
if (pid_output_intermed > 255)
|
||||||
heaters_runtime[h].pid_output = 255;
|
pid_output = 255;
|
||||||
else if (pid_output_intermed < 0)
|
else if (pid_output_intermed < 0)
|
||||||
heaters_runtime[h].pid_output = 0;
|
pid_output = 0;
|
||||||
else
|
else
|
||||||
heaters_runtime[h].pid_output = pid_output_intermed & 0xFF;
|
pid_output = pid_output_intermed & 0xFF;
|
||||||
|
|
||||||
#ifdef DEBUG
|
#ifdef DEBUG
|
||||||
if (debug_flags & DEBUG_PID)
|
if (debug_flags & DEBUG_PID)
|
||||||
sersendf_P(PSTR("T{E:%d, P:%d * %ld = %ld / I:%d * %ld = %ld / D:%d * %ld = %ld # O: %ld = %u}\n"), t_error, heaters_runtime[h].heater_p, heaters_pid[h].p_factor, (int32_t) heaters_runtime[h].heater_p * heaters_pid[h].p_factor / PID_SCALE, heaters_runtime[h].heater_i, heaters_pid[h].i_factor, (int32_t) heaters_runtime[h].heater_i * heaters_pid[h].i_factor / PID_SCALE, heaters_runtime[h].heater_d, heaters_pid[h].d_factor, (int32_t) heaters_runtime[h].heater_d * heaters_pid[h].d_factor / PID_SCALE, pid_output_intermed, heaters_runtime[h].pid_output);
|
sersendf_P(PSTR("T{E:%d, P:%d * %ld = %ld / I:%d * %ld = %ld / D:%d * %ld = %ld # O: %ld = %u}\n"), t_error, heater_p, heaters_pid[h].p_factor, (int32_t) heater_p * heaters_pid[h].p_factor / PID_SCALE, heaters_runtime[h].heater_i, heaters_pid[h].i_factor, (int32_t) heaters_runtime[h].heater_i * heaters_pid[h].i_factor / PID_SCALE, heater_d, heaters_pid[h].d_factor, (int32_t) heater_d * heaters_pid[h].d_factor / PID_SCALE, pid_output_intermed, pid_output);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
heater_set(h, heaters_runtime[h].pid_output);
|
#ifdef HEATER_SANITY_CHECK
|
||||||
|
// check heater sanity
|
||||||
|
// implementation is a moving window with some slow-down to compensate for thermal mass
|
||||||
|
if (target_temp > (current_temp + TEMP_HYSTERESIS)) {
|
||||||
|
// heating
|
||||||
|
if (current_temp > heaters_runtime[h].sane_temperature)
|
||||||
|
// hotter than sane- good since we're heating unless too hot
|
||||||
|
heaters_runtime[h].sane_temperature = current_temp;
|
||||||
|
else {
|
||||||
|
if (heaters_runtime[h].sanity_counter < 40)
|
||||||
|
heaters_runtime[h].sanity_counter++;
|
||||||
|
else {
|
||||||
|
heaters_runtime[h].sanity_counter = 0;
|
||||||
|
// ratchet up expected temp
|
||||||
|
heaters_runtime[h].sane_temperature++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// limit to target, so if we overshoot by too much for too long an error is flagged
|
||||||
|
if (heaters_runtime[h].sane_temperature > target_temp)
|
||||||
|
heaters_runtime[h].sane_temperature = target_temp;
|
||||||
|
}
|
||||||
|
else if (target_temp < (current_temp - TEMP_HYSTERESIS)) {
|
||||||
|
// cooling
|
||||||
|
if (current_temp < heaters_runtime[h].sane_temperature)
|
||||||
|
// cooler than sane- good since we're cooling
|
||||||
|
heaters_runtime[h].sane_temperature = current_temp;
|
||||||
|
else {
|
||||||
|
if (heaters_runtime[h].sanity_counter < 125)
|
||||||
|
heaters_runtime[h].sanity_counter++;
|
||||||
|
else {
|
||||||
|
heaters_runtime[h].sanity_counter = 0;
|
||||||
|
// ratchet down expected temp
|
||||||
|
heaters_runtime[h].sane_temperature--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// if we're at or below 60 celsius, don't freak out if we can't drop any more.
|
||||||
|
if (current_temp <= 240)
|
||||||
|
heaters_runtime[h].sane_temperature = current_temp;
|
||||||
|
// limit to target, so if we don't cool down for too long an error is flagged
|
||||||
|
else if (heaters_runtime[h].sane_temperature < target_temp)
|
||||||
|
heaters_runtime[h].sane_temperature = target_temp;
|
||||||
|
}
|
||||||
|
// we're within HYSTERESIS of our target
|
||||||
|
else {
|
||||||
|
heaters_runtime[h].sane_temperature = current_temp;
|
||||||
|
heaters_runtime[h].sanity_counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare where we're at to where we should be
|
||||||
|
if (labs(current_temp - heaters_runtime[h].sane_temperature) > TEMP_HYSTERESIS) {
|
||||||
|
// no change, or change in wrong direction for a long time- heater is broken!
|
||||||
|
pid_output = 0;
|
||||||
|
sersendf_P(PSTR("!! heater %d broken- temp is %d.%dC, target is %d.%dC, didn't reach %d.%dC in %d0 milliseconds\n"), h, current_temp >> 2, (current_temp & 3) * 25, target_temp >> 2, (target_temp & 3) * 25, heaters_runtime[h].sane_temperature >> 2, (heaters_runtime[h].sane_temperature & 3) * 25, heaters_runtime[h].sanity_counter);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
heater_set(h, pid_output);
|
||||||
}
|
}
|
||||||
|
|
||||||
void heater_set(uint8_t index, uint8_t value) {
|
void heater_set(uint8_t index, uint8_t value) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue