pid: Scale PID Parameters with Sampling Rate

- Breaking changes
This commit is contained in:
linuswck 2024-10-17 17:36:06 +08:00
parent e560d8f1eb
commit f4c761c5ca
4 changed files with 50 additions and 18 deletions

View File

@ -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_upper_limit(70)
await kirdy.thermostat.set_temp_mon_lower_limit(0) 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. # Configure the thermostat ADC Filter Setting / PID Update Rate / Report Rate.
# The ADC sampling rate determines the report and pid update rate. # The ADC sampling rate determines the report and pid update rate.
# The chosen filter and sampling rate affects the noise of the readings. # The chosen filter and sampling rate affects the noise of the readings.
# For details, please refer to the AD7172 datasheet. # For details, please refer to the AD7172 datasheet.
await kirdy.thermostat.config_temp_adc_filter(FilterConfig.Sinc5Sinc1With50hz60HzRejection.f16sps) 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 # Configure thermostat to run in PID control mode
await kirdy.thermostat.set_pid_control_mode() await kirdy.thermostat.set_pid_control_mode()

View File

@ -45,6 +45,7 @@ class PIDAutotune:
raise ValueError('setpoint must be specified') raise ValueError('setpoint must be specified')
self._inputs = deque(maxlen=round(lookback / sampletime)) self._inputs = deque(maxlen=round(lookback / sampletime))
self._sampletime = sampletime
self._setpoint = setpoint self._setpoint = setpoint
self._outputstep = out_step self._outputstep = out_step
self._noiseband = noiseband self._noiseband = noiseband
@ -69,6 +70,7 @@ class PIDAutotune:
self._out_min = -step self._out_min = -step
self._noiseband = noiseband self._noiseband = noiseband
self._inputs = deque(maxlen=round(lookback / sampletime)) self._inputs = deque(maxlen=round(lookback / sampletime))
self._sampletime = sampletime
def setReady(self): def setReady(self):
self._state = PIDAutotuneState.STATE_READY self._state = PIDAutotuneState.STATE_READY
@ -96,8 +98,8 @@ class PIDAutotune:
def get_tec_pid (self): def get_tec_pid (self):
divisors = self._tuning_rules["tyreus-luyben"] divisors = self._tuning_rules["tyreus-luyben"]
kp = self._Ku * divisors[0] kp = self._Ku * divisors[0]
ki = divisors[1] * self._Ku / self._Pu ki = divisors[1] * self._Ku / self._Pu / (1 / self._sampletime)
kd = divisors[2] * self._Ku * self._Pu kd = divisors[2] * self._Ku * self._Pu * (1 / self._sampletime)
return kp, ki, kd return kp, ki, kd
def get_pid_parameters(self, tuning_rule='ziegler-nichols'): def get_pid_parameters(self, tuning_rule='ziegler-nichols'):
@ -109,8 +111,8 @@ class PIDAutotune:
""" """
divisors = self._tuning_rules[tuning_rule] divisors = self._tuning_rules[tuning_rule]
kp = self._Ku * divisors[0] kp = self._Ku * divisors[0]
ki = divisors[1] * self._Ku / self._Pu ki = divisors[1] * self._Ku / self._Pu / (1 / self._sampletime)
kd = divisors[2] * self._Ku * self._Pu kd = divisors[2] * self._Ku * self._Pu * (1 / self._sampletime)
return PIDAutotune.PIDParams(kp, ki, kd) return PIDAutotune.PIDParams(kp, ki, kd)
def run(self, input_val, time_input): def run(self, input_val, time_input):

View File

@ -142,16 +142,16 @@ impl PidState {
self.controller.parameters = pid_params; 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 { match param {
PidSettings::Kp => { PidSettings::Kp => {
self.controller.parameters.kp = val; self.controller.parameters.kp = val;
} }
PidSettings::Ki => { PidSettings::Ki => {
self.controller.parameters.ki = val; self.controller.parameters.ki = val * curr_rate;
} }
PidSettings::Kd => { PidSettings::Kd => {
self.controller.parameters.kd = val; self.controller.parameters.kd = val / curr_rate;
} }
PidSettings::Min => { PidSettings::Min => {
self.controller.parameters.output_min = val; 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) { pub fn reset_pid_state(&mut self) {
self.controller.u1 = 0.0; self.controller.u1 = 0.0;
self.controller.x1 = 0.0; self.controller.x1 = 0.0;

View File

@ -453,7 +453,8 @@ impl Thermostat {
} }
pub fn set_pid(&mut self, param: PidSettings, val: f32) { 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) { 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) { 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(); self.ad7172.set_sinc5_sinc1_filter(index, odr).unwrap();
} }
pub fn set_temp_adc_sinc3_filter(&mut self, index: u8, odr: ad7172::SingleChODR) { 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(); self.ad7172.set_sinc3_filter(index, odr).unwrap();
} }
pub fn set_temp_adc_sinc5_sinc1_with_postfilter(&mut self, index: u8, odr: ad7172::PostFilter) { 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 self.ad7172
.set_sinc5_sinc1_with_50hz_60hz_rejection(index, odr) .set_sinc5_sinc1_with_50hz_60hz_rejection(index, odr)
.unwrap(); .unwrap();
} }
pub fn set_temp_adc_sinc3_fine_filter(&mut self, index: u8, rate: f32) { 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(); self.ad7172.set_sinc3_fine_filter(index, rate).unwrap();
} }
@ -548,7 +564,7 @@ impl Thermostat {
pid_engaged: self.get_pid_engaged(), pid_engaged: self.get_pid_engaged(),
temperature_setpoint: self.pid_ctrl_ch0.get_pid_setpoint(), temperature_setpoint: self.pid_ctrl_ch0.get_pid_setpoint(),
tec_settings: self.get_tec_settings(), 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_adc_settings: temp_adc_settings,
temp_mon_settings: self.get_temp_mon_settings(), temp_mon_settings: self.get_temp_mon_settings(),
thermistor_params: self.get_steinhart_hart(), thermistor_params: self.get_steinhart_hart(),
@ -580,6 +596,7 @@ impl Thermostat {
self.set_pid_engaged(settings.pid_engaged); self.set_pid_engaged(settings.pid_engaged);
self.pid_ctrl_ch0.apply_pid_params(settings.pid_params); 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); self.set_temperature_setpoint(settings.temperature_setpoint);
if !settings.pid_engaged { if !settings.pid_engaged {
self.set_i(settings.tec_settings.i_set.value); self.set_i(settings.tec_settings.i_set.value);