Skip to content

Commit

Permalink
controllers/heater: add dynamic gain to PID
Browse files Browse the repository at this point in the history
- Adds a dynamic error proportional gain over Kp values found with
  autotune. When outside of stable target region, this dynamic gain
  increase the Kp directly proportional to error and changes the
  proportionality modality of the PID-controller from ON_MEASUREMENT to
  ON_ERROR. Within the stable region, autotune discovered Kp is together
  with ON_MEASUREMENT proportionality is restored.
  • Loading branch information
s-t-a-n committed Oct 12, 2024
1 parent f3443d2 commit eb18554
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 13 deletions.
37 changes: 29 additions & 8 deletions src/kaskas/io/controllers/heater.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,6 @@ class Heater {
public:
// todo: make an autotuner to find limits of normal usage experimentally
struct Config {
// maximal error from setpoint in degrees that is allowed to happen for timewindow stable_timewindow until
// thermal runaway is considered
// double stable_hysteresis_c = 1.0;
time_s stable_timewindow = time_m(10);

// minimal change in degrees within timewindow changing_timewindow
Expand Down Expand Up @@ -139,6 +136,9 @@ class Heater {
double steady_state_hysteresis = 0.3;
time_s cooldown_min_length = time_s(180);

double dynamic_gain_factor = 0; // for every degree of error from sp, adds `dynamic gain` to Kp
time_s dynamic_gain_interval = time_s(60); // update gain every n seconds

HardwareStack::Idx climate_temperature_idx;
HardwareStack::Idx heating_surface_temperature_idx;
HardwareStack::Idx heating_element_idx;
Expand All @@ -153,6 +153,7 @@ class Heater {
_surface_temperature(_hws.analog_sensor(_cfg.heating_surface_temperature_idx)),
_climate_temperature(_hws.analog_sensor(_cfg.climate_temperature_idx)),
_temperature_source(&_surface_temperature), _update_interval(IntervalTimer(_cfg.pid_cfg.sample_interval)),
_dynamic_gain_interval(IntervalTimer(_cfg.dynamic_gain_interval)),
_climate_trp(std::move(_cfg.climate_trp_cfg)) {}

void initialize() {
Expand All @@ -171,13 +172,31 @@ class Heater {

_pid.new_reading(temperature());
const auto response = _pid.response();
const auto normalized_response = _pid.setpoint() > 0 ? response / _cfg.pid_cfg.output_upper_limit : 0;
spn_assert(normalized_response >= 0.0 && normalized_response <= 1.0);
spn_assert(_cfg.pid_cfg.output_upper_limit > 0);
auto normalized_response = _pid.setpoint() > 0 ? response / _cfg.pid_cfg.output_upper_limit : 0;
spn_expect(normalized_response >= 0.0 && normalized_response <= 1.0);
normalized_response = std::clamp(normalized_response, 0.0, 1.0);
_heating_element.fade_to(guarded_setpoint(normalized_response));

update_state();

_climate_trp.update(state(), _climate_temperature.value());

if (_cfg.dynamic_gain_factor > 0 && _dynamic_gain_interval.expired()) {
auto tunings = _pid.tunings();
auto proportionality = PID::Proportionality::ON_MEASUREMENT;
if (state() == State::HEATING && temperature() < setpoint()) {
tunings.Kp = _cfg.pid_cfg.tunings.Kp * (1 + _cfg.dynamic_gain_factor * error());
tunings.Ki = _cfg.pid_cfg.tunings.Ki / (1 + _cfg.dynamic_gain_factor * error());
proportionality = PID::Proportionality::ON_ERROR;
} else {
if (tunings == _cfg.pid_cfg.tunings) return;
tunings = _cfg.pid_cfg.tunings;
}
DBG("Heater: setting new tunings: {%.2f:%.2f:%.2f} over original {%.2f:%.2f:%.2f}", tunings.Kp,
tunings.Ki, tunings.Kd, _cfg.pid_cfg.tunings.Kp, _cfg.pid_cfg.tunings.Ki, _cfg.pid_cfg.tunings.Kd);
_pid.set_tunings(tunings, proportionality);
}
}
}

Expand Down Expand Up @@ -219,6 +238,7 @@ class Heater {
block_until_setpoint(cfg.startpoint);
set_target_setpoint(cfg.setpoint);
const auto process_setter = [&](double pwm_value) {
spn_assert(_cfg.pid_cfg.output_upper_limit - _cfg.pid_cfg.output_lower_limit > 0);
const auto normalized_response = (pwm_value - _cfg.pid_cfg.output_lower_limit)
/ (_cfg.pid_cfg.output_upper_limit - _cfg.pid_cfg.output_lower_limit);
const auto guarded_normalized_response = guarded_setpoint(normalized_response);
Expand All @@ -241,8 +261,9 @@ class Heater {
PID::Tunings tunings() const { return _pid.tunings(); }

void set_target_setpoint(const Value setpoint) {
_pid.set_target_setpoint(std::min(_cfg.max_heater_setpoint, setpoint));
_climate_trp.adjust_setpoint(setpoint);
auto clamped_setpoint = std::min(_cfg.max_heater_setpoint, setpoint);
_pid.set_target_setpoint(clamped_setpoint);
_climate_trp.adjust_setpoint(clamped_setpoint);
}
Value setpoint() const { return _pid.setpoint(); }

Expand Down Expand Up @@ -277,7 +298,6 @@ class Heater {

/// When any temperature readings are out of limits, shutdown the system
void guard_temperature_limits() {
//
if (_surface_temperature.value() < MIN_SURFACE_TEMPERATURE
|| _surface_temperature.value() > MAX_SURFACE_TEMPERATURE) {
spn::throw_exception(spn::assertion_exception("Heater: Heater element temperature out of limits"));
Expand Down Expand Up @@ -334,6 +354,7 @@ class Heater {
Timer _cooled_down_for = Timer{};

IntervalTimer _update_interval;
IntervalTimer _dynamic_gain_interval;

ThermalRunAway _climate_trp;
};
Expand Down
11 changes: 6 additions & 5 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ void setup() {
.output_lower_limit = 20,
.output_upper_limit = 90,
.sample_interval = ventilation_sample_interval,
.direction = PID::Direction::FORWARD},
.proportionality = PID::Proportionality::ON_MEASUREMENT},
.minimal_duty_cycle = 0.21,
.schedule_cfg =
Schedule::Config{
Expand All @@ -244,13 +244,14 @@ void setup() {
.heater_cfg =
Heater::Config{
.pid_cfg =
PID::Config{//
.tunings =
PID::Tunings{.Kp = 62.590051, .Ki = 0.152824, .Kd = 0},
PID::Config{.tunings = PID::Tunings{.Kp = 62.590051, .Ki = 0.152824, .Kd = 0},
.output_lower_limit = 0,
.output_upper_limit = 255,
.sample_interval = heating_sample_interval},
.sample_interval = heating_sample_interval,
.proportionality = PID::Proportionality::ON_MEASUREMENT},
.max_heater_setpoint = max_heater_setpoint,
.dynamic_gain_factor = 0.8,
.climate_temperature_idx = meta::ENUM_IDX(DataProviders::CLIMATE_TEMP),
.heating_surface_temperature_idx = meta::ENUM_IDX(DataProviders::HEATING_SURFACE_TEMP),
.heating_element_idx = meta::ENUM_IDX(DataProviders::HEATING_ELEMENT),
.climate_trp_cfg = Heater::ThermalRunAway::Config{.stable_timewindow = time_m(30),
Expand Down

0 comments on commit eb18554

Please sign in to comment.