#include "temp.h" /** \file \brief Manage temperature sensors \note All temperatures are stored as 14.2 fixed point in Teacup, so we have a range of 0 - 16383.75 deg Celsius at a precision of 0.25 deg. That includes the thermistor table, which is why you can't copy and paste one from other firmwares which don't do this. */ #include #ifdef __AVR__ #include #include #endif #include "simulator.h" #include "arduino.h" #include "serial.h" #include "debug.h" #ifndef EXTRUDER #include "sersendf.h" #endif #include "heater.h" #ifdef TEMP_INTERCOM #include "intercom.h" #endif #ifdef TEMP_MAX6675 #include "spi.h" #endif #ifdef TEMP_THERMISTOR #include "analog.h" #include "thermistortable.h" #endif #ifdef TEMP_AD595 #include "analog.h" #endif typedef enum { PRESENT, TCOPEN } temp_flags_enum; /// holds metadata for each temperature sensor typedef struct { temp_type_t temp_type; ///< type of sensor uint8_t temp_pin; ///< pin that sensor is on heater_t heater; ///< associated heater if any uint8_t additional; ///< additional, sensor type specifc config } temp_sensor_definition_t; #undef DEFINE_TEMP_SENSOR /// help build list of sensors from entries in config.h #ifndef SIMULATOR #define DEFINE_TEMP_SENSOR(name, type, pin, additional) { (type), (pin ## _ADC), (HEATER_ ## name), (additional) }, #else #define DEFINE_TEMP_SENSOR(name, type, pin, additional) { (type), (TEMP_SENSOR_ ## name), (HEATER_ ## name), (additional) }, #endif static const temp_sensor_definition_t temp_sensors[NUM_TEMP_SENSORS] = { #include "config_wrapper.h" }; #undef DEFINE_TEMP_SENSOR /// this struct holds the runtime sensor data- read temperatures, targets, etc struct { temp_flags_enum temp_flags; ///< flags uint16_t last_read_temp; ///< last received reading uint16_t target_temp; ///< manipulate attached heater to attempt to achieve this value uint16_t temp_residency; ///< how long have we been close to target temperature in temp ticks? uint16_t next_read_time; ///< how long until we can read this sensor again? } temp_sensors_runtime[NUM_TEMP_SENSORS]; /// Set up temp sensors. void temp_init() { temp_sensor_t i; for (i = 0; i < NUM_TEMP_SENSORS; i++) { switch(temp_sensors[i].temp_type) { #ifdef TEMP_MAX6675 case TT_MAX6675: // Note that MAX6675's Chip Select pin is currently hardcoded to SS. // This isn't neccessary. See also spi.h. spi_deselect_max6675(); // Intentionally no break, we might have more than one sensor type. #endif #ifdef TEMP_THERMISTOR // handled by analog_init() /* case TT_THERMISTOR: break;*/ #endif #ifdef TEMP_AD595 // handled by analog_init() /* case TT_AD595: break;*/ #endif #ifdef TEMP_INTERCOM case TT_INTERCOM: // Enable the RS485 transceiver SET_OUTPUT(RX_ENABLE_PIN); SET_OUTPUT(TX_ENABLE_PIN); WRITE(RX_ENABLE_PIN,0); disable_transmit(); intercom_init(); send_temperature(0, 0); // Intentionally no break. #endif default: /* prevent compiler warning */ break; } } } /// called every 10ms from clock.c - check all temp sensors that are ready for checking void temp_sensor_tick() { temp_sensor_t i = 0; for (; i < NUM_TEMP_SENSORS; i++) { if (temp_sensors_runtime[i].next_read_time) { temp_sensors_runtime[i].next_read_time--; } else { uint16_t temp = 0; //time to deal with this temp sensor switch(temp_sensors[i].temp_type) { #ifdef TEMP_MAX6675 case TT_MAX6675: // Note: value reading in this section was rewritten without // testing when spi.c/.h was introduced. --Traumflug spi_select_max6675(); // No delay required, see // https://github.com/triffid/Teacup_Firmware/issues/22 // read MSB temp = spi_rw(0) << 8; // read LSB temp |= spi_rw(0); spi_deselect_max6675(); temp_sensors_runtime[i].temp_flags = 0; if ((temp & 0x8002) == 0) { // got "device id" temp_sensors_runtime[i].temp_flags |= PRESENT; if (temp & 4) { // thermocouple open temp_sensors_runtime[i].temp_flags |= TCOPEN; } else { temp = temp >> 3; } } // this number depends on how frequently temp_sensor_tick is called. the MAX6675 can give a reading every 0.22s, so set this to about 250ms temp_sensors_runtime[i].next_read_time = 25; break; #endif /* TEMP_MAX6675 */ #ifdef TEMP_THERMISTOR case TT_THERMISTOR: do { uint8_t j, table_num; //Read current temperature temp = analog_read(i); // for thermistors the thermistor table number is in the additional field table_num = temp_sensors[i].additional; //Calculate real temperature based on lookup table for (j = 1; j < NUMTEMPS; j++) { if (pgm_read_word(&(temptable[table_num][j][0])) > temp) { // Thermistor table is already in 14.2 fixed point #ifndef EXTRUDER if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR("pin:%d Raw ADC:%d table entry: %d"),temp_sensors[i].temp_pin,temp,j); #endif // Linear interpolating temperature value // y = ((x - x₀)y₁ + (x₁-x)y₀ ) / (x₁ - x₀) // y = temp // x = ADC reading // x₀= temptable[j-1][0] // x₁= temptable[j][0] // y₀= temptable[j-1][1] // y₁= temptable[j][1] // y = // Wikipedia's example linear interpolation formula. temp = ( // ((x - x₀)y₁ ((uint32_t)temp - pgm_read_word(&(temptable[table_num][j-1][0]))) * pgm_read_word(&(temptable[table_num][j][1])) // + + // (x₁-x) (pgm_read_word(&(temptable[table_num][j][0])) - (uint32_t)temp) // y₀ ) * pgm_read_word(&(temptable[table_num][j-1][1]))) // / / // (x₁ - x₀) (pgm_read_word(&(temptable[table_num][j][0])) - pgm_read_word(&(temptable[table_num][j-1][0]))); #ifndef EXTRUDER if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR(" temp:%d.%d"),temp/4,(temp%4)*25); #endif break; } } #ifndef EXTRUDER if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR(" Sensor:%d\n"),i); #endif //Clamp for overflows if (j == NUMTEMPS) temp = temptable[table_num][NUMTEMPS-1][1]; temp_sensors_runtime[i].next_read_time = 0; } while (0); break; #endif /* TEMP_THERMISTOR */ #ifdef TEMP_AD595 case TT_AD595: temp = analog_read(i); // convert // >>8 instead of >>10 because internal temp is stored as 14.2 fixed point temp = (temp * 500L) >> 8; temp_sensors_runtime[i].next_read_time = 0; break; #endif /* TEMP_AD595 */ #ifdef TEMP_PT100 case TT_PT100: #warning TODO: PT100 code break; #endif /* TEMP_PT100 */ #ifdef TEMP_INTERCOM case TT_INTERCOM: temp = read_temperature(temp_sensors[i].temp_pin); temp_sensors_runtime[i].next_read_time = 25; break; #endif /* TEMP_INTERCOM */ #ifdef TEMP_DUMMY case TT_DUMMY: temp = temp_sensors_runtime[i].last_read_temp; if (temp_sensors_runtime[i].target_temp > temp) temp++; else if (temp_sensors_runtime[i].target_temp < temp) temp--; temp_sensors_runtime[i].next_read_time = 0; break; #endif /* TEMP_DUMMY */ default: /* prevent compiler warning */ break; } /* Exponentially Weighted Moving Average alpha constant for smoothing noisy sensors. Instrument Engineer's Handbook, 4th ed, Vol 2 p126 says values of 0.05 to 0.1 for TEMP_EWMA are typical. */ #ifndef TEMP_EWMA #define TEMP_EWMA 1.0 #endif #define EWMA_SCALE 1024L #define EWMA_ALPHA ((long) (TEMP_EWMA * EWMA_SCALE)) temp_sensors_runtime[i].last_read_temp = (uint16_t) ((EWMA_ALPHA * temp + (EWMA_SCALE-EWMA_ALPHA) * temp_sensors_runtime[i].last_read_temp ) / EWMA_SCALE); } if (labs((int16_t)(temp_sensors_runtime[i].last_read_temp - temp_sensors_runtime[i].target_temp)) < (TEMP_HYSTERESIS*4)) { if (temp_sensors_runtime[i].temp_residency < (TEMP_RESIDENCY_TIME*120)) temp_sensors_runtime[i].temp_residency++; } else { // Deal with flakey sensors which occasionally report a wrong value // by setting residency back, but not entirely to zero. if (temp_sensors_runtime[i].temp_residency > 10) temp_sensors_runtime[i].temp_residency -= 10; else temp_sensors_runtime[i].temp_residency = 0; } if (temp_sensors[i].heater < NUM_HEATERS) { heater_tick(temp_sensors[i].heater, temp_sensors[i].temp_type, temp_sensors_runtime[i].last_read_temp, temp_sensors_runtime[i].target_temp); } if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR("DU temp: {%d %d %d.%d}"), i, temp_sensors_runtime[i].last_read_temp, temp_sensors_runtime[i].last_read_temp / 4, (temp_sensors_runtime[i].last_read_temp & 0x03) * 25); } if (DEBUG_PID && (debug_flags & DEBUG_PID)) sersendf_P(PSTR("\n")); } /** * Report whether all temp sensors in use are reading their target * temperatures. Used for M116 and friends. */ uint8_t temp_achieved() { temp_sensor_t i; uint8_t all_ok = 255; for (i = 0; i < NUM_TEMP_SENSORS; i++) { if (temp_sensors_runtime[i].target_temp > 0 && temp_sensors_runtime[i].temp_residency < (TEMP_RESIDENCY_TIME*100)) all_ok = 0; } return all_ok; } /// specify a target temperature /// \param index sensor to set a target for /// \param temperature target temperature to aim for void temp_set(temp_sensor_t index, uint16_t temperature) { if (index >= NUM_TEMP_SENSORS) return; // only reset residency if temp really changed if (temp_sensors_runtime[index].target_temp != temperature) { temp_sensors_runtime[index].target_temp = temperature; temp_sensors_runtime[index].temp_residency = 0; #ifdef TEMP_INTERCOM if (temp_sensors[index].temp_type == TT_INTERCOM) send_temperature(temp_sensors[index].temp_pin, temperature); #endif } } /// return most recent reading for a sensor /// \param index sensor to read uint16_t temp_get(temp_sensor_t index) { if (index >= NUM_TEMP_SENSORS) return 0; return temp_sensors_runtime[index].last_read_temp; } // extruder doesn't have sersendf_P #ifndef EXTRUDER static void single_temp_print(temp_sensor_t index) { uint8_t c = (temp_sensors_runtime[index].last_read_temp & 3) * 25; sersendf_P(PSTR("%u.%u"), temp_sensors_runtime[index].last_read_temp >> 2, c); #ifdef REPORT_TARGET_TEMPS sersendf_P(PSTR("/")); c = (temp_sensors_runtime[index].target_temp & 3) * 25; sersendf_P(PSTR("%u.%u"), temp_sensors_runtime[index].target_temp >> 2, c); #endif } /// send temperatures to host /// \param index sensor value to send void temp_print(temp_sensor_t index) { if (index == TEMP_SENSOR_none) { // standard behaviour #ifdef HEATER_EXTRUDER sersendf_P(PSTR("T:")); single_temp_print(TEMP_SENSOR_extruder); #endif #ifdef HEATER_BED sersendf_P(PSTR(" B:")); single_temp_print(TEMP_SENSOR_bed); #endif } else { if (index >= NUM_TEMP_SENSORS) return; sersendf_P(PSTR("T[%su]:"), index); single_temp_print(index); } serial_writechar('\n'); } #endif