2024-01-17 12:49:57 +08:00
|
|
|
use smoltcp::time::{Duration, Instant};
|
|
|
|
use uom::si::{
|
|
|
|
f64::{
|
|
|
|
ElectricPotential,
|
|
|
|
ElectricalResistance,
|
|
|
|
ThermodynamicTemperature,
|
|
|
|
Time,
|
|
|
|
},
|
|
|
|
electric_potential::volt,
|
|
|
|
electrical_resistance::ohm,
|
|
|
|
thermodynamic_temperature::degree_celsius,
|
|
|
|
time::millisecond,
|
|
|
|
};
|
|
|
|
use crate::thermostat::{
|
|
|
|
ad7172,
|
|
|
|
steinhart_hart as sh,
|
|
|
|
};
|
|
|
|
use crate::pid::pid;
|
|
|
|
|
|
|
|
const R_INNER: f64 = 2.0 * 5100.0;
|
|
|
|
const VREF_SENS: f64 = 3.3 / 2.0;
|
|
|
|
|
2024-01-17 17:01:25 +08:00
|
|
|
pub struct PidState {
|
2024-01-17 12:49:57 +08:00
|
|
|
pub adc_data: Option<u32>,
|
|
|
|
pub adc_calibration: ad7172::ChannelCalibration,
|
2024-01-17 17:13:27 +08:00
|
|
|
pub sample_ts: Instant,
|
|
|
|
pub sampling_interval: Duration,
|
2024-01-17 12:49:57 +08:00
|
|
|
/// i_set 0A center point
|
2024-01-17 17:13:27 +08:00
|
|
|
pub center_point: ElectricPotential,
|
|
|
|
pub dac_volt: ElectricPotential,
|
2024-01-17 12:49:57 +08:00
|
|
|
pub pid_engaged: bool,
|
|
|
|
pub pid: pid::Controller,
|
|
|
|
pub sh: sh::Parameters,
|
|
|
|
}
|
|
|
|
|
2024-01-17 17:01:25 +08:00
|
|
|
impl PidState {
|
2024-01-17 16:00:02 +08:00
|
|
|
fn adc_calibration(mut self, adc_calibration: ad7172::ChannelCalibration) -> Self {
|
|
|
|
self.adc_calibration = adc_calibration;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-17 17:01:25 +08:00
|
|
|
impl Default for PidState {
|
2024-01-17 16:00:02 +08:00
|
|
|
fn default() -> Self {
|
2024-01-17 17:01:25 +08:00
|
|
|
PidState {
|
2024-01-17 12:49:57 +08:00
|
|
|
adc_data: None,
|
2024-01-17 16:00:02 +08:00
|
|
|
adc_calibration: ad7172::ChannelCalibration::default(),
|
2024-01-17 17:13:27 +08:00
|
|
|
sample_ts: Instant::from_secs(0),
|
2024-01-17 12:49:57 +08:00
|
|
|
// default: 10 Hz
|
2024-01-17 17:13:27 +08:00
|
|
|
sampling_interval: Duration::from_millis(100),
|
|
|
|
center_point: ElectricPotential::new::<volt>(1.5),
|
|
|
|
dac_volt: ElectricPotential::new::<volt>(0.0),
|
2024-01-17 12:49:57 +08:00
|
|
|
pid_engaged: false,
|
|
|
|
pid: pid::Controller::new(pid::Parameters::default()),
|
|
|
|
sh: sh::Parameters::default(),
|
|
|
|
}
|
|
|
|
}
|
2024-01-17 16:00:02 +08:00
|
|
|
}
|
|
|
|
|
2024-01-17 17:01:25 +08:00
|
|
|
impl PidState {
|
2024-01-17 16:00:02 +08:00
|
|
|
pub fn new(adc_calibration: ad7172::ChannelCalibration) -> Self {
|
2024-01-17 17:01:25 +08:00
|
|
|
PidState::default().adc_calibration(adc_calibration)
|
2024-01-17 16:00:02 +08:00
|
|
|
}
|
2024-01-17 12:49:57 +08:00
|
|
|
|
|
|
|
pub fn update(&mut self, now: Instant, adc_data: u32) {
|
|
|
|
self.adc_data = if adc_data == ad7172::MAX_VALUE {
|
|
|
|
// this means there is no thermistor plugged into the ADC.
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(adc_data)
|
|
|
|
};
|
2024-01-17 17:13:27 +08:00
|
|
|
self.sampling_interval = now - self.sample_ts;
|
|
|
|
self.sample_ts = now;
|
2024-01-17 12:49:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Update PID state on ADC input, calculate new DAC output
|
|
|
|
pub fn update_pid(&mut self) -> Option<f64> {
|
|
|
|
let temperature = self.get_temperature()?
|
|
|
|
.get::<degree_celsius>();
|
|
|
|
let pid_output = self.pid.update(temperature);
|
|
|
|
Some(pid_output)
|
|
|
|
}
|
|
|
|
|
2024-01-17 17:13:27 +08:00
|
|
|
pub fn get_sample_ts(&self) -> Time {
|
|
|
|
Time::new::<millisecond>(self.sample_ts.total_millis() as f64)
|
2024-01-17 12:49:57 +08:00
|
|
|
}
|
|
|
|
|
2024-01-17 17:13:27 +08:00
|
|
|
pub fn get_sampling_interval(&self) -> Time {
|
|
|
|
Time::new::<millisecond>(self.sampling_interval.total_millis() as f64)
|
2024-01-17 12:49:57 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_adc(&self) -> Option<ElectricPotential> {
|
|
|
|
Some(self.adc_calibration.convert_data(self.adc_data?))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get `SENS[01]` input resistance
|
|
|
|
pub fn get_sens(&self) -> Option<ElectricalResistance> {
|
|
|
|
let r_inner = ElectricalResistance::new::<ohm>(R_INNER);
|
|
|
|
let vref = ElectricPotential::new::<volt>(VREF_SENS);
|
|
|
|
let adc_input = self.get_adc()?;
|
|
|
|
let r = r_inner * adc_input / (vref - adc_input);
|
|
|
|
Some(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_temperature(&self) -> Option<ThermodynamicTemperature> {
|
|
|
|
let r = self.get_sens()?;
|
|
|
|
let temperature = self.sh.get_temperature(r);
|
|
|
|
Some(temperature)
|
|
|
|
}
|
|
|
|
}
|