diff --git a/pykirdy/asyncio_exmaple.py b/pykirdy/asyncio_exmaple.py index 9587c1e..8a2e91d 100644 --- a/pykirdy/asyncio_exmaple.py +++ b/pykirdy/asyncio_exmaple.py @@ -85,21 +85,22 @@ async def ld_thermostat_cfg(kirdy: Kirdy): await kirdy.thermostat.set_temp_mon_upper_limit(70) await kirdy.thermostat.set_temp_mon_lower_limit(0) - # Configure the thermostat PID parameters. - # You can configure the PID parameter by the included autotune script. - await kirdy.thermostat.set_temperature_setpoint(25) - await kirdy.thermostat.set_pid_kp(0.15668282198105507) - await kirdy.thermostat.set_pid_ki(0.002135962407793784) - await kirdy.thermostat.set_pid_kd(0.829254515277143) - await kirdy.thermostat.set_pid_output_max(1.0) - await kirdy.thermostat.set_pid_output_min(-1.0) - # Configure the thermostat ADC Filter Setting / PID Update Rate / Report Rate. # The ADC sampling rate determines the report and pid update rate. # The chosen filter and sampling rate affects the noise of the readings. # For details, please refer to the AD7172 datasheet. await kirdy.thermostat.config_temp_adc_filter(FilterConfig.Sinc5Sinc1With50hz60HzRejection.f16sps) + # Configure the thermostat PID parameters. + # You can configure the PID parameter by the included autotune script. + # You should configure the filter sampling rate first before applying pid parameters. + await kirdy.thermostat.set_temperature_setpoint(25) + await kirdy.thermostat.set_pid_kp(0.15668282198105507) + await kirdy.thermostat.set_pid_ki(0.0001281321) + await kirdy.thermostat.set_pid_kd(13.82367) + await kirdy.thermostat.set_pid_output_max(1.0) + await kirdy.thermostat.set_pid_output_min(-1.0) + # Configure thermostat to run in PID control mode await kirdy.thermostat.set_pid_control_mode() diff --git a/pykirdy/pid_autotune.py b/pykirdy/pid_autotune.py index 22b3eab..9a767c7 100644 --- a/pykirdy/pid_autotune.py +++ b/pykirdy/pid_autotune.py @@ -45,6 +45,7 @@ class PIDAutotune: raise ValueError('setpoint must be specified') self._inputs = deque(maxlen=round(lookback / sampletime)) + self._sampletime = sampletime self._setpoint = setpoint self._outputstep = out_step self._noiseband = noiseband @@ -69,6 +70,7 @@ class PIDAutotune: self._out_min = -step self._noiseband = noiseband self._inputs = deque(maxlen=round(lookback / sampletime)) + self._sampletime = sampletime def setReady(self): self._state = PIDAutotuneState.STATE_READY @@ -96,8 +98,8 @@ class PIDAutotune: def get_tec_pid (self): divisors = self._tuning_rules["tyreus-luyben"] kp = self._Ku * divisors[0] - ki = divisors[1] * self._Ku / self._Pu - kd = divisors[2] * self._Ku * self._Pu + ki = divisors[1] * self._Ku / self._Pu / (1 / self._sampletime) + kd = divisors[2] * self._Ku * self._Pu * (1 / self._sampletime) return kp, ki, kd def get_pid_parameters(self, tuning_rule='ziegler-nichols'): @@ -109,8 +111,8 @@ class PIDAutotune: """ divisors = self._tuning_rules[tuning_rule] kp = self._Ku * divisors[0] - ki = divisors[1] * self._Ku / self._Pu - kd = divisors[2] * self._Ku * self._Pu + ki = divisors[1] * self._Ku / self._Pu / (1 / self._sampletime) + kd = divisors[2] * self._Ku * self._Pu * (1 / self._sampletime) return PIDAutotune.PIDParams(kp, ki, kd) def run(self, input_val, time_input): diff --git a/src/thermostat/pid_state.rs b/src/thermostat/pid_state.rs index cc4f4b9..5fb644f 100644 --- a/src/thermostat/pid_state.rs +++ b/src/thermostat/pid_state.rs @@ -142,16 +142,16 @@ impl PidState { self.controller.parameters = pid_params; } - pub fn set_pid_params(&mut self, param: PidSettings, val: f32) { + pub fn set_pid_params(&mut self, param: PidSettings, val: f32, curr_rate: f32) { match param { PidSettings::Kp => { self.controller.parameters.kp = val; } PidSettings::Ki => { - self.controller.parameters.ki = val; + self.controller.parameters.ki = val * curr_rate; } PidSettings::Kd => { - self.controller.parameters.kd = val; + self.controller.parameters.kd = val / curr_rate; } PidSettings::Min => { self.controller.parameters.output_min = val; @@ -162,6 +162,18 @@ impl PidState { } } + pub fn update_pid_params_with_new_sampling_rate(&mut self, old_rate: f32, new_rate: f32) { + self.controller.parameters.ki = self.controller.parameters.ki * old_rate / new_rate; + self.controller.parameters.kd = self.controller.parameters.kd * new_rate / old_rate; + } + + pub fn get_abs_pid_params(&mut self, curr_rate: f32) -> Parameters { + let mut pid_params = self.controller.parameters.clone(); + pid_params.ki = self.controller.parameters.ki / curr_rate; + pid_params.kd = self.controller.parameters.kd * curr_rate; + pid_params + } + pub fn reset_pid_state(&mut self) { self.controller.u1 = 0.0; self.controller.x1 = 0.0; diff --git a/src/thermostat/thermostat.rs b/src/thermostat/thermostat.rs index d9f3cd4..8151332 100644 --- a/src/thermostat/thermostat.rs +++ b/src/thermostat/thermostat.rs @@ -453,7 +453,8 @@ impl Thermostat { } pub fn set_pid(&mut self, param: PidSettings, val: f32) { - self.pid_ctrl_ch0.set_pid_params(param, val); + let curr_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3; + self.pid_ctrl_ch0.set_pid_params(param, val, curr_rate); } pub fn set_sh_beta(&mut self, beta: f32) { @@ -492,20 +493,35 @@ impl Thermostat { } pub fn set_temp_adc_sinc5_sinc1_filter(&mut self, index: u8, odr: ad7172::SingleChODR) { + let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3; + let new_rate = odr.output_rate().unwrap(); + self.pid_ctrl_ch0 + .update_pid_params_with_new_sampling_rate(old_rate, new_rate); self.ad7172.set_sinc5_sinc1_filter(index, odr).unwrap(); } pub fn set_temp_adc_sinc3_filter(&mut self, index: u8, odr: ad7172::SingleChODR) { + let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3; + let new_rate = odr.output_rate().unwrap(); + self.pid_ctrl_ch0 + .update_pid_params_with_new_sampling_rate(old_rate, new_rate); self.ad7172.set_sinc3_filter(index, odr).unwrap(); } pub fn set_temp_adc_sinc5_sinc1_with_postfilter(&mut self, index: u8, odr: ad7172::PostFilter) { + let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3; + let new_rate = odr.output_rate().unwrap(); + self.pid_ctrl_ch0 + .update_pid_params_with_new_sampling_rate(old_rate, new_rate); self.ad7172 .set_sinc5_sinc1_with_50hz_60hz_rejection(index, odr) .unwrap(); } pub fn set_temp_adc_sinc3_fine_filter(&mut self, index: u8, rate: f32) { + let old_rate = self.ad7172.get_filter_type_and_rate(0).unwrap().3; + self.pid_ctrl_ch0 + .update_pid_params_with_new_sampling_rate(old_rate, rate); self.ad7172.set_sinc3_fine_filter(index, rate).unwrap(); } @@ -548,7 +564,7 @@ impl Thermostat { pid_engaged: self.get_pid_engaged(), temperature_setpoint: self.pid_ctrl_ch0.get_pid_setpoint(), tec_settings: self.get_tec_settings(), - pid_params: self.get_pid_settings(), + pid_params: self.pid_ctrl_ch0.get_abs_pid_params(temp_adc_settings.rate.unwrap()), temp_adc_settings: temp_adc_settings, temp_mon_settings: self.get_temp_mon_settings(), thermistor_params: self.get_steinhart_hart(), @@ -580,6 +596,7 @@ impl Thermostat { self.set_pid_engaged(settings.pid_engaged); self.pid_ctrl_ch0.apply_pid_params(settings.pid_params); + self.pid_ctrl_ch0.update_pid_params_with_new_sampling_rate(settings.temp_adc_settings.rate.unwrap(), 1.0); self.set_temperature_setpoint(settings.temperature_setpoint); if !settings.pid_engaged { self.set_i(settings.tec_settings.i_set.value);