PageRenderTime 19ms CodeModel.GetById 2ms app.highlight 14ms RepoModel.GetById 1ms app.codeStats 1ms

/firmware/src/shared/Heater.cc

http://github.com/makerbot/G3Firmware
C++ | 207 lines | 139 code | 34 blank | 34 comment | 23 complexity | 209f56c7f4503a7b435443bf75273377 MD5 | raw file
  1/*
  2 * Copyright 2010 by Adam Mayer	 <adam@makerbot.com>
  3 *
  4 * This program is free software: you can redistribute it and/or modify
  5 * it under the terms of the GNU General Public License as published by
  6 * the Free Software Foundation, either version 3 of the License, or
  7 * (at your option) any later version.
  8 *
  9 * This program is distributed in the hope that it will be useful,
 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 12 * GNU General Public License for more details.
 13 *
 14 * You should have received a copy of the GNU General Public License
 15 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 16 */
 17
 18#include "Configuration.hh"
 19#include "Heater.hh"
 20#include "HeatingElement.hh"
 21#include "Thermistor.hh"
 22//#include "ExtruderBoard.hh"
 23#include "Eeprom.hh"
 24#include "EepromMap.hh"
 25
 26#define DEFAULT_P 7.0
 27#define DEFAULT_I 0.325
 28#define DEFAULT_D 36.0
 29
 30
 31// EEPROM map
 32#define P_TERM_OFFSET      0
 33#define I_TERM_OFFSET      2
 34#define D_TERM_OFFSET      4
 35
 36
 37/// Offset to compensate for range clipping and bleed-off
 38#define HEATER_OFFSET_ADJUSTMENT 0
 39
 40/// PID bypass: If the set point is more than this many degrees over the
 41///             current temperature, bypass the PID loop altogether.
 42#define PID_BYPASS_DELTA 15
 43
 44/// Number of bad sensor readings we need to get in a row before shutting off the heater
 45#define SENSOR_MAX_BAD_READINGS 5
 46
 47/// If we read a temperature higher than this, shut down the heater
 48#define HEATER_CUTOFF_TEMPERATURE 280
 49
 50Heater::Heater(TemperatureSensor& sensor_in,
 51               HeatingElement& element_in,
 52               micros_t sample_interval_micros_in,
 53               uint16_t eeprom_base_in) :
 54		sensor(sensor_in),
 55		element(element_in),
 56		sample_interval_micros(sample_interval_micros_in),
 57		eeprom_base(eeprom_base_in)
 58{
 59	reset();
 60}
 61
 62void Heater::reset() {
 63	// TODO: Reset sensor, element here?
 64
 65	current_temperature = 0;
 66
 67	fail_state = false;
 68	fail_count = 0;
 69
 70	float p = eeprom::getEepromFixed16(eeprom_base+P_TERM_OFFSET,DEFAULT_P);
 71	float i = eeprom::getEepromFixed16(eeprom_base+I_TERM_OFFSET,DEFAULT_I);
 72	float d = eeprom::getEepromFixed16(eeprom_base+D_TERM_OFFSET,DEFAULT_D);
 73
 74	pid.reset();
 75	if (p == 0 && i == 0 && d == 0) {
 76		p = DEFAULT_P; i = DEFAULT_I; d = DEFAULT_D;
 77	}
 78	pid.setPGain(p);
 79	pid.setIGain(i);
 80	pid.setDGain(d);
 81	pid.setTarget(0);
 82	next_pid_timeout.start(UPDATE_INTERVAL_MICROS);
 83	next_sense_timeout.start(sample_interval_micros);
 84}
 85
 86void Heater::set_target_temperature(int temp)
 87{
 88	pid.setTarget(temp);
 89}
 90
 91// We now define target hysteresis in absolute degrees.  The original
 92// implementation (+/-5%) was giving us swings of 10% in either direction
 93// *before* any artifacts of process instability came in.
 94#define TARGET_HYSTERESIS 2
 95
 96bool Heater::has_reached_target_temperature()
 97{
 98	return (current_temperature >= (pid.getTarget() - TARGET_HYSTERESIS)) &&
 99			(current_temperature <= (pid.getTarget() + TARGET_HYSTERESIS));
100}
101
102int Heater::get_set_temperature() {
103	return pid.getTarget();
104}
105
106int Heater::get_current_temperature()
107{
108	return sensor.getTemperature();
109}
110
111int Heater::getPIDErrorTerm() {
112	return pid.getErrorTerm();
113}
114
115int Heater::getPIDDeltaTerm() {
116	return pid.getDeltaTerm();
117}
118
119int Heater::getPIDLastOutput() {
120	return pid.getLastOutput();
121}
122
123void Heater::manage_temperature()
124{
125	if (fail_state) {
126		return;
127	}
128
129	if (next_sense_timeout.hasElapsed()) {
130		next_sense_timeout.start(sample_interval_micros);
131		switch (sensor.update()) {
132		case TemperatureSensor::SS_ADC_BUSY:
133		case TemperatureSensor::SS_ADC_WAITING:
134			// We're waiting for the ADC, so don't update the temperature yet.
135			current_temperature = 2;
136			return;
137			break;
138		case TemperatureSensor::SS_OK:
139			// Result was ok, so reset the fail counter, and continue.
140			fail_count = 0;
141			break;
142		case TemperatureSensor::SS_ERROR_UNPLUGGED:
143		default:
144			// If we get too many bad readings in a row, shut down the heater.
145			fail_count++;
146
147			if (fail_count > SENSOR_MAX_BAD_READINGS) {
148				fail();
149			}
150			current_temperature = 3;
151			return;
152			break;
153		}
154
155		current_temperature = get_current_temperature();
156		if (current_temperature > HEATER_CUTOFF_TEMPERATURE) {
157			fail();
158			return;
159		}
160	}
161	if (next_pid_timeout.hasElapsed()) {
162		next_pid_timeout.start(UPDATE_INTERVAL_MICROS);
163
164		int delta = pid.getTarget() - current_temperature;
165
166		if( bypassing_PID && (delta < PID_BYPASS_DELTA) ) {
167			bypassing_PID = false;
168
169			pid.reset_state();
170		}
171		else if ( !bypassing_PID && (delta > PID_BYPASS_DELTA + 10) ) {
172			bypassing_PID = true;
173		}
174
175		if( bypassing_PID ) {
176			set_output(255);
177		}
178		else {
179			int mv = pid.calculate(current_temperature);
180			// offset value to compensate for heat bleed-off.
181			// There are probably more elegant ways to do this,
182			// but this works pretty well.
183			mv += HEATER_OFFSET_ADJUSTMENT;
184			// clamp value
185			if (mv < 0) { mv = 0; }
186			if (mv >255) { mv = 255; }
187			if (pid.getTarget() == 0) { mv = 0; }
188			set_output(mv);
189		}
190	}
191}
192
193void Heater::set_output(uint8_t value)
194{
195	element.setHeatingElement(value);
196}
197
198void Heater::fail()
199{
200	fail_state = true;
201	set_output(0);
202}
203
204bool Heater::has_failed()
205{
206	return fail_state;
207}