forked from M-Labs/thermostat
pid: integrate time_delta to free gain parameters from sampling period
Fixes Gitea issue #22
This commit is contained in:
parent
b7e6cdbec2
commit
e9e46b29cf
|
@ -110,8 +110,8 @@ formatted as line-delimited JSON.
|
||||||
| `pid` | Show PID configuration |
|
| `pid` | Show PID configuration |
|
||||||
| `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature |
|
| `pid <0/1> target <deg_celsius>` | Set the PID controller target temperature |
|
||||||
| `pid <0/1> kp <value>` | Set proportional gain |
|
| `pid <0/1> kp <value>` | Set proportional gain |
|
||||||
| `pid <0/1> ki <value>` | Set integral gain (unit: 10 Hz) |
|
| `pid <0/1> ki <value>` | Set integral gain |
|
||||||
| `pid <0/1> kd <value>` | Set differential gain (unit: 0.1 seconds) |
|
| `pid <0/1> kd <value>` | Set differential gain |
|
||||||
| `pid <0/1> output_min <amp>` | Set mininum output |
|
| `pid <0/1> output_min <amp>` | Set mininum output |
|
||||||
| `pid <0/1> output_max <amp>` | Set maximum output |
|
| `pid <0/1> output_max <amp>` | Set maximum output |
|
||||||
| `pid <0/1> integral_min <value>` | Set integral lower bound |
|
| `pid <0/1> integral_min <value>` | Set integral lower bound |
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
use smoltcp::time::Instant;
|
use smoltcp::time::{Duration, Instant};
|
||||||
use uom::si::{
|
use uom::si::{
|
||||||
f64::{
|
f64::{
|
||||||
ElectricPotential,
|
ElectricPotential,
|
||||||
ElectricalResistance,
|
ElectricalResistance,
|
||||||
ThermodynamicTemperature,
|
ThermodynamicTemperature,
|
||||||
|
Time,
|
||||||
},
|
},
|
||||||
electric_potential::volt,
|
electric_potential::volt,
|
||||||
electrical_resistance::ohm,
|
electrical_resistance::ohm,
|
||||||
thermodynamic_temperature::degree_celsius,
|
thermodynamic_temperature::degree_celsius,
|
||||||
|
time::millisecond,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
ad7172,
|
ad7172,
|
||||||
|
@ -23,6 +25,7 @@ pub struct ChannelState {
|
||||||
pub adc_data: Option<u32>,
|
pub adc_data: Option<u32>,
|
||||||
pub adc_calibration: ad7172::ChannelCalibration,
|
pub adc_calibration: ad7172::ChannelCalibration,
|
||||||
pub adc_time: Instant,
|
pub adc_time: Instant,
|
||||||
|
pub adc_interval: Duration,
|
||||||
/// VREF for the TEC (1.5V)
|
/// VREF for the TEC (1.5V)
|
||||||
pub vref: ElectricPotential,
|
pub vref: ElectricPotential,
|
||||||
/// i_set 0A center point
|
/// i_set 0A center point
|
||||||
|
@ -39,6 +42,8 @@ impl ChannelState {
|
||||||
adc_data: None,
|
adc_data: None,
|
||||||
adc_calibration,
|
adc_calibration,
|
||||||
adc_time: Instant::from_secs(0),
|
adc_time: Instant::from_secs(0),
|
||||||
|
// default: 10 Hz
|
||||||
|
adc_interval: Duration::from_millis(100),
|
||||||
// updated later with Channels.read_vref()
|
// updated later with Channels.read_vref()
|
||||||
vref: ElectricPotential::new::<volt>(1.5),
|
vref: ElectricPotential::new::<volt>(1.5),
|
||||||
center: CenterPoint::Vref,
|
center: CenterPoint::Vref,
|
||||||
|
@ -56,6 +61,7 @@ impl ChannelState {
|
||||||
} else {
|
} else {
|
||||||
Some(adc_data)
|
Some(adc_data)
|
||||||
};
|
};
|
||||||
|
self.adc_interval = now - self.adc_time;
|
||||||
self.adc_time = now;
|
self.adc_time = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,10 +69,18 @@ impl ChannelState {
|
||||||
pub fn update_pid(&mut self) -> Option<f64> {
|
pub fn update_pid(&mut self) -> 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);
|
let pid_output = self.pid.update(temperature, self.get_adc_interval());
|
||||||
Some(pid_output)
|
Some(pid_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_adc_time(&self) -> Time {
|
||||||
|
Time::new::<millisecond>(self.adc_time.total_millis() as f64)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_adc_interval(&self) -> Time {
|
||||||
|
Time::new::<millisecond>(self.adc_interval.total_millis() as f64)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_adc(&self) -> Option<ElectricPotential> {
|
pub fn get_adc(&self) -> Option<ElectricPotential> {
|
||||||
Some(self.adc_calibration.convert_data(self.adc_data?))
|
Some(self.adc_calibration.convert_data(self.adc_data?))
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ use serde::{Serialize, Serializer};
|
||||||
use smoltcp::time::Instant;
|
use smoltcp::time::Instant;
|
||||||
use stm32f4xx_hal::hal;
|
use stm32f4xx_hal::hal;
|
||||||
use uom::si::{
|
use uom::si::{
|
||||||
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance},
|
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance, Time},
|
||||||
electric_potential::{millivolt, volt},
|
electric_potential::{millivolt, volt},
|
||||||
electric_current::ampere,
|
electric_current::ampere,
|
||||||
electrical_resistance::ohm,
|
electrical_resistance::ohm,
|
||||||
|
@ -425,7 +425,8 @@ impl Channels {
|
||||||
);
|
);
|
||||||
Report {
|
Report {
|
||||||
channel,
|
channel,
|
||||||
time: state.adc_time.total_millis(),
|
time: state.get_adc_time(),
|
||||||
|
interval: state.get_adc_interval(),
|
||||||
adc: state.get_adc(),
|
adc: state.get_adc(),
|
||||||
sens: state.get_sens(),
|
sens: state.get_sens(),
|
||||||
temperature: state.get_temperature()
|
temperature: state.get_temperature()
|
||||||
|
@ -510,7 +511,8 @@ type JsonBuffer = Vec<u8, U1024>;
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct Report {
|
pub struct Report {
|
||||||
channel: usize,
|
channel: usize,
|
||||||
time: i64,
|
time: Time,
|
||||||
|
interval: Time,
|
||||||
adc: Option<ElectricPotential>,
|
adc: Option<ElectricPotential>,
|
||||||
sens: Option<ElectricalResistance>,
|
sens: Option<ElectricalResistance>,
|
||||||
temperature: Option<f64>,
|
temperature: Option<f64>,
|
||||||
|
|
16
src/pid.rs
16
src/pid.rs
|
@ -1,4 +1,8 @@
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
|
use uom::si::{
|
||||||
|
f64::Time,
|
||||||
|
time::second,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Parameters {
|
pub struct Parameters {
|
||||||
|
@ -45,7 +49,9 @@ impl Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, input: f64) -> f64 {
|
pub fn update(&mut self, input: f64, time_delta: Time) -> f64 {
|
||||||
|
let time_delta = time_delta.get::<second>();
|
||||||
|
|
||||||
// error
|
// error
|
||||||
let error = input - self.target;
|
let error = input - self.target;
|
||||||
|
|
||||||
|
@ -53,7 +59,7 @@ impl Controller {
|
||||||
let p = f64::from(self.parameters.kp) * error;
|
let p = f64::from(self.parameters.kp) * error;
|
||||||
|
|
||||||
// integral
|
// integral
|
||||||
self.integral += f64::from(self.parameters.ki) * error;
|
self.integral += f64::from(self.parameters.ki) * error * time_delta;
|
||||||
if self.integral < self.parameters.integral_min.into() {
|
if self.integral < self.parameters.integral_min.into() {
|
||||||
self.integral = self.parameters.integral_min.into();
|
self.integral = self.parameters.integral_min.into();
|
||||||
}
|
}
|
||||||
|
@ -64,8 +70,10 @@ impl Controller {
|
||||||
|
|
||||||
// derivative
|
// derivative
|
||||||
let d = match self.last_input {
|
let d = match self.last_input {
|
||||||
None => 0.0,
|
None =>
|
||||||
Some(last_input) => f64::from(self.parameters.kd) * (input - last_input),
|
0.0,
|
||||||
|
Some(last_input) =>
|
||||||
|
f64::from(self.parameters.kd) * (input - last_input) / time_delta,
|
||||||
};
|
};
|
||||||
self.last_input = Some(input);
|
self.last_input = Some(input);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue