forked from M-Labs/thermostat
pid: anti-windup when compliance voltage reached
This commit is contained in:
parent
3067b356c5
commit
193d54a0a6
@ -3,11 +3,13 @@ use uom::si::{
|
|||||||
f64::{
|
f64::{
|
||||||
ElectricPotential,
|
ElectricPotential,
|
||||||
ElectricalResistance,
|
ElectricalResistance,
|
||||||
|
ElectricCurrent,
|
||||||
ThermodynamicTemperature,
|
ThermodynamicTemperature,
|
||||||
Time,
|
Time,
|
||||||
},
|
},
|
||||||
electric_potential::volt,
|
electric_potential::volt,
|
||||||
electrical_resistance::ohm,
|
electrical_resistance::ohm,
|
||||||
|
// electric_current::ampere,
|
||||||
thermodynamic_temperature::degree_celsius,
|
thermodynamic_temperature::degree_celsius,
|
||||||
time::millisecond,
|
time::millisecond,
|
||||||
};
|
};
|
||||||
@ -66,10 +68,10 @@ impl ChannelState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update PID state on ADC input, calculate new DAC output
|
/// Update PID state on ADC input, calculate new DAC output
|
||||||
pub fn update_pid(&mut self) -> Option<f64> {
|
pub fn update_pid(&mut self, current: ElectricCurrent) -> Option<f64> {
|
||||||
let temperature = self.get_temperature()?
|
let temperature = self.get_temperature()?
|
||||||
.get::<degree_celsius>();
|
.get::<degree_celsius>();
|
||||||
let pid_output = self.pid.update(temperature, self.get_adc_interval());
|
let pid_output = self.pid.update(temperature, self.get_adc_interval(), current);
|
||||||
Some(pid_output)
|
Some(pid_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,10 +73,10 @@ impl Channels {
|
|||||||
pub fn poll_adc(&mut self, instant: Instant) -> Option<u8> {
|
pub fn poll_adc(&mut self, instant: Instant) -> Option<u8> {
|
||||||
self.adc.data_ready().unwrap().map(|channel| {
|
self.adc.data_ready().unwrap().map(|channel| {
|
||||||
let data = self.adc.read_data().unwrap();
|
let data = self.adc.read_data().unwrap();
|
||||||
|
let current = self.get_tec_i(channel.into());
|
||||||
let state = self.channel_state(channel);
|
let state = self.channel_state(channel);
|
||||||
state.update(instant, data);
|
state.update(instant, data);
|
||||||
match state.update_pid() {
|
match state.update_pid(current) {
|
||||||
Some(pid_output) if state.pid_engaged => {
|
Some(pid_output) if state.pid_engaged => {
|
||||||
// Forward PID output to i_set DAC
|
// Forward PID output to i_set DAC
|
||||||
self.set_i(channel.into(), ElectricCurrent::new::<ampere>(pid_output));
|
self.set_i(channel.into(), ElectricCurrent::new::<ampere>(pid_output));
|
||||||
|
16
src/pid.rs
16
src/pid.rs
@ -1,9 +1,13 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use uom::si::{
|
use uom::si::{
|
||||||
f64::Time,
|
f64::{Time, ElectricCurrent},
|
||||||
time::second,
|
time::second,
|
||||||
|
electric_current::ampere,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Allowable current error for integral accumulation
|
||||||
|
const CURRENT_ERROR_MAX: f64 = 0.1;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Parameters {
|
pub struct Parameters {
|
||||||
/// Gain coefficient for proportional term
|
/// Gain coefficient for proportional term
|
||||||
@ -56,7 +60,7 @@ impl Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, input: f64, time_delta: Time) -> f64 {
|
pub fn update(&mut self, input: f64, time_delta: Time, current: ElectricCurrent) -> f64 {
|
||||||
let time_delta = time_delta.get::<second>();
|
let time_delta = time_delta.get::<second>();
|
||||||
|
|
||||||
// error
|
// error
|
||||||
@ -67,8 +71,12 @@ impl Controller {
|
|||||||
|
|
||||||
// integral
|
// integral
|
||||||
if let Some(last_output_val) = self.last_output {
|
if let Some(last_output_val) = self.last_output {
|
||||||
|
let electric_current_error = ElectricCurrent::new::<ampere>(last_output_val) - current;
|
||||||
// anti integral windup
|
// anti integral windup
|
||||||
if last_output_val < self.parameters.output_max.into() && last_output_val > self.parameters.output_min.into() {
|
if last_output_val < self.parameters.output_max.into() &&
|
||||||
|
last_output_val > self.parameters.output_min.into() &&
|
||||||
|
electric_current_error < ElectricCurrent::new::<ampere>(CURRENT_ERROR_MAX) &&
|
||||||
|
electric_current_error > -ElectricCurrent::new::<ampere>(CURRENT_ERROR_MAX) {
|
||||||
self.integral += error * time_delta;
|
self.integral += error * time_delta;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,7 +176,7 @@ mod test {
|
|||||||
while !values.iter().all(|value| target.contains(value)) && total_t < CYCLE_LIMIT {
|
while !values.iter().all(|value| target.contains(value)) && total_t < CYCLE_LIMIT {
|
||||||
let next_t = (t + 1) % DELAY;
|
let next_t = (t + 1) % DELAY;
|
||||||
// Feed the oldest temperature
|
// Feed the oldest temperature
|
||||||
let output = pid.update(values[next_t], Time::new::<second>(1.0));
|
let output = pid.update(values[next_t], Time::new::<second>(1.0), values[next_t]);
|
||||||
// Overwrite oldest with previous temperature - output
|
// Overwrite oldest with previous temperature - output
|
||||||
values[next_t] = values[t] + output - (values[t] - DEFAULT) * LOSS;
|
values[next_t] = values[t] + output - (values[t] - DEFAULT) * LOSS;
|
||||||
t = next_t;
|
t = next_t;
|
||||||
|
Loading…
Reference in New Issue
Block a user