diff --git a/heater-avr.c b/heater-avr.c new file mode 100644 index 0000000..663f5ac --- /dev/null +++ b/heater-avr.c @@ -0,0 +1,217 @@ + +/** \file + \brief Manage heaters, including PID and PWM, AVR specific part. +*/ + +#if defined TEACUP_C_INCLUDE && defined __AVR__ + +#include +#include "pinio.h" +#include "crc.h" +#include "sersendf.h" +#include "debug.h" + +/// \struct heater_definition_t +/// \brief simply holds pinout data- port, pin, pwm channel if used +typedef struct { + volatile uint8_t *heater_port; ///< pointer to port. DDR is inferred from this pointer too + uint8_t heater_pin; ///< heater pin, not masked. eg for PB3 enter '3' here, or PB3_PIN or similar + volatile uint8_t *heater_pwm; ///< pointer to 8-bit PWM register, eg OCR0A (8-bit) or ORC3L (low byte, 16-bit) +} heater_definition_t; + +#undef DEFINE_HEATER +/// \brief helper macro to fill heater definition struct from config.h +#define DEFINE_HEATER(name, pin, pwm) { &(pin ## _WPORT), pin ## _PIN, \ + pwm ? (pin ## _PWM) : NULL}, +static const heater_definition_t heaters[NUM_HEATERS] = +{ + #include "config_wrapper.h" +}; +#undef DEFINE_HEATER + + +/// \brief initialise heater subsystem +/// Set directions, initialise PWM timers, read PID factors from eeprom, etc +void heater_init() { + heater_t i; + + // setup PWM timers: fast PWM + // Warning 2012-01-11: these are not consistent across all AVRs + TCCR0A = MASK(WGM01) | MASK(WGM00); + // PWM frequencies in TCCR0B, see page 108 of the ATmega644 reference. + TCCR0B = MASK(CS00); // F_CPU / 256 (about 78(62.5) kHz on a 20(16) MHz chip) + #ifndef FAST_PWM + TCCR0B = MASK(CS00) | MASK(CS02); // F_CPU / 256 / 1024 (about 76(61) Hz) + #endif + TIMSK0 = 0; + OCR0A = 0; + OCR0B = 0; + + // timer 1 is used for stepping + + #ifdef TCCR2A + TCCR2A = MASK(WGM21) | MASK(WGM20); + // PWM frequencies in TCCR2B, see page 156 of the ATmega644 reference. + TCCR2B = MASK(CS20); // F_CPU / 256 (about 78(62.5) kHz on a 20(16) MHz chip) + #ifndef FAST_PWM + TCCR2B = MASK(CS20) | MASK(CS21) | MASK(CS22); // F_CPU / 256 / 1024 + #endif + TIMSK2 = 0; + OCR2A = 0; + OCR2B = 0; + #endif + + #ifdef TCCR3A + TCCR3A = MASK(WGM30); + TCCR3B = MASK(WGM32) | MASK(CS30); + TIMSK3 = 0; + OCR3A = 0; + OCR3B = 0; + #endif + + #ifdef TCCR4A + #ifdef TIMER4_IS_10_BIT + // ATmega16/32U4 fourth timer is a special 10 bit timer + TCCR4A = MASK(PWM4A) | MASK(PWM4B) ; // enable A and B + TCCR4C = MASK(PWM4D); // and D + TCCR4D = MASK(WGM40); // Phase correct + TCCR4B = MASK(CS40); // no prescaler + #ifndef FAST_PWM + TCCR4B = MASK(CS40) | MASK(CS42) | MASK(CS43); // 16 MHz / 1024 / 256 + //TCCR4B = MASK(CS40) | MASK(CS41) | MASK(CS43); // 16 MHz / 4096 / 256 + #endif + TC4H = 0; // clear high bits + OCR4C = 0xff; // 8 bit max count at top before reset + #else + TCCR4A = MASK(WGM40); + TCCR4B = MASK(WGM42) | MASK(CS40); + #endif + TIMSK4 = 0; + OCR4A = 0; + OCR4B = 0; + #ifdef OCR4D + OCR4D = 0; + #endif + #endif + + #ifdef TCCR5A + TCCR5A = MASK(WGM50); + TCCR5B = MASK(WGM52) | MASK(CS50); + TIMSK5 = 0; + OCR5A = 0; + OCR5B = 0; + #endif + + // setup pins + for (i = 0; i < NUM_HEATERS; i++) { + if (heaters[i].heater_pwm) { + *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) { + case (uint16_t) &OCR0A: + TCCR0A |= MASK(COM0A1); + break; + case (uint16_t) &OCR0B: + TCCR0A |= MASK(COM0B1); + break; + #ifdef TCCR2A + case (uint16_t) &OCR2A: + TCCR2A |= MASK(COM2A1); + break; + case (uint16_t) &OCR2B: + TCCR2A |= MASK(COM2B1); + break; + #endif + #ifdef TCCR3A + case (uint16_t) &OCR3AL: + TCCR3A |= MASK(COM3A1); + break; + case (uint16_t) &OCR3BL: + TCCR3A |= MASK(COM3B1); + break; + #ifdef COM3C1 + case (uint16_t) &OCR3CL: + TCCR3A |= MASK(COM3C1); + break; + #endif + #endif + #ifdef TCCR4A + #if defined (OCR4AL) + case (uint16_t) &OCR4AL: + TCCR4A |= MASK(COM4A1); + break; + case (uint16_t) &OCR4BL: + TCCR4A |= MASK(COM4B1); + break; + case (uint16_t) &OCR4CL: + TCCR4A |= MASK(COM4C1); + break; + #else + // 10 bit timer + case (uint16_t) &OCR4A: + TCCR4A |= MASK(COM4A1); + break; + case (uint16_t) &OCR4B: + TCCR4A |= MASK(COM4B1); + break; + #ifdef OCR4D + case (uint16_t) &OCR4D: + TCCR4C |= MASK(COM4D1); + break; + #endif + #endif + #endif + #ifdef TCCR5A + case (uint16_t) &OCR5AL: + TCCR5A |= MASK(COM5A1); + break; + case (uint16_t) &OCR5BL: + TCCR5A |= MASK(COM5B1); + break; + case (uint16_t) &OCR5CL: + TCCR5A |= MASK(COM5C1); + break; + #endif + } + } + + pid_init(i); + } + + // set all heater pins to output + #undef DEFINE_HEATER + #define DEFINE_HEATER(name, pin, pwm) SET_OUTPUT(pin); WRITE(pin, 0); + #include "config_wrapper.h" + #undef DEFINE_HEATER +} + +/** \brief manually set PWM output + \param index the heater we're setting the output for + \param value the PWM value to write + + anything done by this function is overwritten by heater_tick above if the heater has an associated temp sensor +*/ +void heater_set(heater_t index, uint8_t value) { + if (index >= NUM_HEATERS) + return; + + heaters_runtime[index].heater_output = value; + + if (heaters[index].heater_pwm) { + *(heaters[index].heater_pwm) = value; + + if (DEBUG_PID && (debug_flags & DEBUG_PID)) + sersendf_P(PSTR("PWM{%u = %u}\n"), index, *heaters[index].heater_pwm); + } + else { + if (value >= HEATER_THRESHOLD) + *(heaters[index].heater_port) |= MASK(heaters[index].heater_pin); + else + *(heaters[index].heater_port) &= ~MASK(heaters[index].heater_pin); + } + + if (value) + power_on(); +} + +#endif /* defined TEACUP_C_INCLUDE && defined __AVR__ */ diff --git a/heater.c b/heater.c index 45af37e..70c685b 100644 --- a/heater.c +++ b/heater.c @@ -1,39 +1,25 @@ -#include "heater.h" /** \file - \brief Manage heaters + \brief Manage heaters, including PID and PWM. */ -#include -#include +#include "heater.h" +#define TEACUP_C_INCLUDE +#include "heater-avr.c" +//#include "heater-arm.c" +#undef TEACUP_C_INCLUDE + +#include #include "arduino.h" #include "debug.h" -#include "temp.h" -#include "pinio.h" #include "crc.h" - #ifndef EXTRUDER #include "sersendf.h" #endif - -/// \struct heater_definition_t -/// \brief simply holds pinout data- port, pin, pwm channel if used -typedef struct { - volatile uint8_t *heater_port; ///< pointer to port. DDR is inferred from this pointer too - uint8_t heater_pin; ///< heater pin, not masked. eg for PB3 enter '3' here, or PB3_PIN or similar - volatile uint8_t *heater_pwm; ///< pointer to 8-bit PWM register, eg OCR0A (8-bit) or ORC3L (low byte, 16-bit) -} heater_definition_t; - -#undef DEFINE_HEATER -/// \brief helper macro to fill heater definition struct from config.h -#define DEFINE_HEATER(name, pin, pwm) { &(pin ## _WPORT), pin ## _PIN, \ - pwm ? (pin ## _PWM) : NULL}, -static const heater_definition_t heaters[NUM_HEATERS] = -{ - #include "config_wrapper.h" -}; -#undef DEFINE_HEATER +#ifdef EECONFIG + #include +#endif /** \var heaters_pid @@ -54,36 +40,6 @@ struct { int16_t i_limit; ///< scaled I limit, such that \f$-i_{limit} < i_{factor} < i_{limit}\f$ } heaters_pid[NUM_HEATERS]; -/// \brief this struct holds the runtime heater data- PID integrator history, temperature history, sanity checker -struct { - int16_t heater_i; ///< integrator, \f$-i_{limit} < \sum{4*eC*\Delta t} < i_{limit}\f$ - - uint16_t temp_history[TH_COUNT]; ///< store last TH_COUNT readings in a ring, so we can smooth out our differentiator - uint8_t temp_history_pointer; ///< pointer to last entry in ring - - #ifdef HEATER_SANITY_CHECK - uint16_t sanity_counter; ///< how long things haven't seemed sane - uint16_t sane_temperature; ///< a temperature we consider sane given the heater settings - #endif - - uint8_t heater_output; ///< this is the PID value we eventually send to the heater -} heaters_runtime[NUM_HEATERS]; - -#ifdef BANG_BANG - #define HEATER_THRESHOLD ((BANG_BANG_ON + BANG_BANG_OFF) / 2) -#else - #define HEATER_THRESHOLD 8 -#endif - -/// default scaled P factor, equivalent to 8.0 counts/qC or 32 counts/C -#define DEFAULT_P 8192 -/// default scaled I factor, equivalent to 0.5 counts/(qC*qs) or 8 counts/C*s -#define DEFAULT_I 512 -/// default scaled D factor, equivalent to 24 counts/(qc/(TH_COUNT*qs)) or 192 counts/(C/s) -#define DEFAULT_D 24576 -/// default scaled I limit, equivalent to 384 qC*qs, or 24 C*s -#define DEFAULT_I_LIMIT 384 - #ifdef EECONFIG /// this lives in the eeprom so we can save our PID settings for each heater typedef struct { @@ -97,186 +53,42 @@ typedef struct { EE_factor EEMEM EE_factors[NUM_HEATERS]; #endif /* EECONFIG */ -/// \brief initialise heater subsystem -/// Set directions, initialise PWM timers, read PID factors from eeprom, etc -void heater_init() { - heater_t i; - // setup PWM timers: fast PWM - // Warning 2012-01-11: these are not consistent across all AVRs - TCCR0A = MASK(WGM01) | MASK(WGM00); - // PWM frequencies in TCCR0B, see page 108 of the ATmega644 reference. - TCCR0B = MASK(CS00); // F_CPU / 256 (about 78(62.5) kHz on a 20(16) MHz chip) - #ifndef FAST_PWM - TCCR0B = MASK(CS00) | MASK(CS02); // F_CPU / 256 / 1024 (about 76(61) Hz) - #endif - TIMSK0 = 0; - OCR0A = 0; - OCR0B = 0; +heater_runtime_t heaters_runtime[NUM_HEATERS]; - // timer 1 is used for stepping +/** Inititalise PID data structures. - #ifdef TCCR2A - TCCR2A = MASK(WGM21) | MASK(WGM20); - // PWM frequencies in TCCR2B, see page 156 of the ATmega644 reference. - TCCR2B = MASK(CS20); // F_CPU / 256 (about 78(62.5) kHz on a 20(16) MHz chip) - #ifndef FAST_PWM - TCCR2B = MASK(CS20) | MASK(CS21) | MASK(CS22); // F_CPU / 256 / 1024 - #endif - TIMSK2 = 0; - OCR2A = 0; - OCR2B = 0; + \param i Index of the heater to initialise by Teacup numbering. +*/ +void pid_init(heater_t i) { + + #ifdef HEATER_SANITY_CHECK + // 0 is a "sane" temperature when we're trying to cool down. + heaters_runtime[i].sane_temperature = 0; #endif - #ifdef TCCR3A - TCCR3A = MASK(WGM30); - TCCR3B = MASK(WGM32) | MASK(CS30); - TIMSK3 = 0; - OCR3A = 0; - OCR3B = 0; - #endif + #ifndef BANG_BANG + #ifdef EECONFIG + // Read factors from EEPROM. + 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].d_factor = + eeprom_read_dword((uint32_t *)&EE_factors[i].EE_d_factor); + heaters_pid[i].i_limit = + eeprom_read_word((uint16_t *)&EE_factors[i].EE_i_limit); - #ifdef TCCR4A - #ifdef TIMER4_IS_10_BIT - // ATmega16/32U4 fourth timer is a special 10 bit timer - TCCR4A = MASK(PWM4A) | MASK(PWM4B) ; // enable A and B - TCCR4C = MASK(PWM4D); // and D - TCCR4D = MASK(WGM40); // Phase correct - TCCR4B = MASK(CS40); // no prescaler - #ifndef FAST_PWM - TCCR4B = MASK(CS40) | MASK(CS42) | MASK(CS43); // 16 MHz / 1024 / 256 - //TCCR4B = MASK(CS40) | MASK(CS41) | MASK(CS43); // 16 MHz / 4096 / 256 - #endif - TC4H = 0; // clear high bits - OCR4C = 0xff; // 8 bit max count at top before reset - #else - TCCR4A = MASK(WGM40); - TCCR4B = MASK(WGM42) | MASK(CS40); - #endif - TIMSK4 = 0; - OCR4A = 0; - OCR4B = 0; - #ifdef OCR4D - OCR4D = 0; - #endif - #endif - - #ifdef TCCR5A - TCCR5A = MASK(WGM50); - TCCR5B = MASK(WGM52) | MASK(CS50); - TIMSK5 = 0; - OCR5A = 0; - OCR5B = 0; - #endif - - // setup pins - for (i = 0; i < NUM_HEATERS; i++) { - if (heaters[i].heater_pwm) { - *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) { - case (uint16_t) &OCR0A: - TCCR0A |= MASK(COM0A1); - break; - case (uint16_t) &OCR0B: - TCCR0A |= MASK(COM0B1); - break; - #ifdef TCCR2A - case (uint16_t) &OCR2A: - TCCR2A |= MASK(COM2A1); - break; - case (uint16_t) &OCR2B: - TCCR2A |= MASK(COM2B1); - break; - #endif - #ifdef TCCR3A - case (uint16_t) &OCR3AL: - TCCR3A |= MASK(COM3A1); - break; - case (uint16_t) &OCR3BL: - TCCR3A |= MASK(COM3B1); - break; - #ifdef COM3C1 - case (uint16_t) &OCR3CL: - TCCR3A |= MASK(COM3C1); - break; - #endif - #endif - #ifdef TCCR4A - #if defined (OCR4AL) - case (uint16_t) &OCR4AL: - TCCR4A |= MASK(COM4A1); - break; - case (uint16_t) &OCR4BL: - TCCR4A |= MASK(COM4B1); - break; - case (uint16_t) &OCR4CL: - TCCR4A |= MASK(COM4C1); - break; - #else - // 10 bit timer - case (uint16_t) &OCR4A: - TCCR4A |= MASK(COM4A1); - break; - case (uint16_t) &OCR4B: - TCCR4A |= MASK(COM4B1); - break; - #ifdef OCR4D - case (uint16_t) &OCR4D: - TCCR4C |= MASK(COM4D1); - break; - #endif - #endif - #endif - #ifdef TCCR5A - case (uint16_t) &OCR5AL: - TCCR5A |= MASK(COM5A1); - break; - case (uint16_t) &OCR5BL: - TCCR5A |= MASK(COM5B1); - break; - case (uint16_t) &OCR5CL: - TCCR5A |= MASK(COM5C1); - break; - #endif - } - } - - #ifdef HEATER_SANITY_CHECK - // 0 is a "sane" temperature when we're trying to cool down - heaters_runtime[i].sane_temperature = 0; - #endif - - #ifndef BANG_BANG - #ifdef EECONFIG - // read factors from eeprom - 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].d_factor = - eeprom_read_dword((uint32_t *) &EE_factors[i].EE_d_factor); - heaters_pid[i].i_limit = - eeprom_read_word((uint16_t *) &EE_factors[i].EE_i_limit); - - if (crc_block(&heaters_pid[i].p_factor, 14) != eeprom_read_word((uint16_t *) &EE_factors[i].crc)) - #endif /* EECONFIG */ - { - heaters_pid[i].p_factor = DEFAULT_P; - heaters_pid[i].i_factor = DEFAULT_I; - heaters_pid[i].d_factor = DEFAULT_D; - heaters_pid[i].i_limit = DEFAULT_I_LIMIT; - } - #endif /* BANG_BANG */ - } - - // set all heater pins to output - do { - #undef DEFINE_HEATER - #define DEFINE_HEATER(name, pin, pwm) SET_OUTPUT(pin); WRITE(pin, 0); - #include "config_wrapper.h" - #undef DEFINE_HEATER - } while (0); + if (crc_block(&heaters_pid[i].p_factor, 14) != + eeprom_read_word((uint16_t *)&EE_factors[i].crc)) + #endif /* EECONFIG */ + { + heaters_pid[i].p_factor = DEFAULT_P; + heaters_pid[i].i_factor = DEFAULT_I; + heaters_pid[i].d_factor = DEFAULT_D; + heaters_pid[i].i_limit = DEFAULT_I_LIMIT; + } + #endif /* BANG_BANG */ } /** \brief run heater PID algorithm @@ -415,35 +227,6 @@ void heater_tick(heater_t h, temp_type_t type, uint16_t current_temp, uint16_t t heater_set(h, pid_output); } -/** \brief manually set PWM output - \param index the heater we're setting the output for - \param value the PWM value to write - - anything done by this function is overwritten by heater_tick above if the heater has an associated temp sensor -*/ -void heater_set(heater_t index, uint8_t value) { - if (index >= NUM_HEATERS) - return; - - heaters_runtime[index].heater_output = value; - - if (heaters[index].heater_pwm) { - *(heaters[index].heater_pwm) = value; - - if (DEBUG_PID && (debug_flags & DEBUG_PID)) - sersendf_P(PSTR("PWM{%u = %u}\n"), index, *heaters[index].heater_pwm); - } - else { - if (value >= HEATER_THRESHOLD) - *(heaters[index].heater_port) |= MASK(heaters[index].heater_pin); - else - *(heaters[index].heater_port) &= ~MASK(heaters[index].heater_pin); - } - - if (value) - power_on(); -} - /** \brief check whether all heaters are off */ uint8_t heaters_all_zero() { diff --git a/heater.h b/heater.h index c16e6e3..73a41ba 100644 --- a/heater.h +++ b/heater.h @@ -6,6 +6,30 @@ #include "simulator.h" #include "temp.h" +/// Default scaled P factor, equivalent to 8.0 counts/qC or 32 counts/C. +#define DEFAULT_P 8192 +/// Default scaled I factor, equivalent to 0.5 counts/(qC*qs) or 8 counts/C*s. +#define DEFAULT_I 512 +/// Default scaled D factor, equivalent to 24 counts/(qc/(TH_COUNT*qs)) or +/// 192 counts/(C/s). +#define DEFAULT_D 24576 +/// Default scaled I limit, equivalent to 384 qC*qs, or 24 C*s. +#define DEFAULT_I_LIMIT 384 + +/** \def HEATER_THRESHOLD + + Defines the threshold when to turn a non-PWM heater on and when to turn it + off. Applies only to heaters which have only two states, on and off. + Opposed to those heaters which allow to turn them on gradually as needed, + usually by using PWM. +*/ +#ifdef BANG_BANG + #define HEATER_THRESHOLD ((BANG_BANG_ON + BANG_BANG_OFF) / 2) +#else + #define HEATER_THRESHOLD 8 +#endif + + #undef DEFINE_HEATER #define DEFINE_HEATER(name, pin, pwm) HEATER_ ## name, typedef enum @@ -16,7 +40,36 @@ typedef enum } heater_t; #undef DEFINE_HEATER +/** This struct holds the runtime heater data. + + PID integrator history, temperature history, sanity checker. +*/ +typedef struct { + /// Integrator, \f$-i_{limit} < \sum{4*eC*\Delta t} < i_{limit}\f$ + int16_t heater_i; + + /// Store last TH_COUNT readings in a ring, so we can smooth out our + /// differentiator. + uint16_t temp_history[TH_COUNT]; + /// Pointer to last entry in ring. + uint8_t temp_history_pointer; + + #ifdef HEATER_SANITY_CHECK + /// How long things haven't seemed sane. + uint16_t sanity_counter; + /// A temperature we consider sane given the heater settings. + uint16_t sane_temperature; + #endif + + /// This is the PID value we eventually send to the heater. + uint8_t heater_output; +} heater_runtime_t; + + +extern heater_runtime_t heaters_runtime[]; + void heater_init(void); +void pid_init(heater_t index); void heater_set(heater_t index, uint8_t value); void heater_tick(heater_t h, temp_type_t type, uint16_t current_temp, uint16_t target_temp);