2024-04-23 17:09:26 +08:00
|
|
|
use core::{f32::NAN, marker::PhantomData};
|
|
|
|
|
2024-02-01 15:49:41 +08:00
|
|
|
use log::debug;
|
2024-02-02 14:07:25 +08:00
|
|
|
use miniconf::Tree;
|
2024-04-23 17:09:26 +08:00
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use uom::si::{electric_current::ampere,
|
|
|
|
electric_potential::volt,
|
|
|
|
electrical_resistance::ohm,
|
|
|
|
f32::{ElectricCurrent, ElectricPotential, ElectricalResistance, ThermodynamicTemperature},
|
|
|
|
ratio::ratio,
|
|
|
|
thermodynamic_temperature::degree_celsius};
|
|
|
|
|
|
|
|
use crate::{sys_timer,
|
|
|
|
thermostat::{ad5680,
|
|
|
|
ad7172::{self, FilterType, PostFilter, SingleChODR},
|
|
|
|
max1968::{AdcReadTarget, PwmPinsEnum, MAX1968},
|
|
|
|
pid_state,
|
|
|
|
pid_state::{Parameters as PidParams, PidSettings, PidState},
|
|
|
|
steinhart_hart::Parameters as Sh_Params,
|
|
|
|
temp_mon::{TempMon, TempMonSettings, TempStatus}}};
|
2024-02-15 11:00:53 +08:00
|
|
|
|
2024-01-04 17:13:46 +08:00
|
|
|
pub const R_SENSE: ElectricalResistance = ElectricalResistance {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 0.05,
|
|
|
|
};
|
|
|
|
|
2024-03-04 15:38:55 +08:00
|
|
|
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Default, Tree)]
|
2024-04-23 17:09:26 +08:00
|
|
|
pub struct TempAdcFilter {
|
2024-03-04 15:38:55 +08:00
|
|
|
pub filter_type: FilterType,
|
|
|
|
pub sinc5sinc1odr: Option<SingleChODR>,
|
|
|
|
pub sinc3odr: Option<SingleChODR>,
|
|
|
|
pub sinc5sinc1postfilter: Option<PostFilter>,
|
|
|
|
pub sinc3fineodr: Option<f32>,
|
|
|
|
pub rate: Option<f32>,
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Tree)]
|
2024-01-18 15:14:24 +08:00
|
|
|
pub struct TecSettings {
|
2024-03-04 15:48:19 +08:00
|
|
|
pub default_pwr_on: bool,
|
2024-01-05 15:00:50 +08:00
|
|
|
pub center_pt: ElectricPotential,
|
|
|
|
pub max_v_set: ElectricPotential,
|
|
|
|
pub max_i_pos_set: ElectricCurrent,
|
|
|
|
pub max_i_neg_set: ElectricCurrent,
|
|
|
|
pub i_set: ElectricCurrent,
|
2024-02-29 16:47:03 +08:00
|
|
|
pub vref: ElectricPotential,
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
2024-01-04 17:13:46 +08:00
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
impl TecSettings {
|
2024-01-05 15:00:50 +08:00
|
|
|
pub const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
2024-01-24 12:25:45 +08:00
|
|
|
value: 3.0,
|
2024-01-05 15:00:50 +08:00
|
|
|
};
|
|
|
|
pub const TEC_VSEC_BIAS_V: ElectricPotential = ElectricPotential {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 1.65,
|
|
|
|
};
|
2024-04-23 17:09:26 +08:00
|
|
|
|
2024-01-05 15:00:50 +08:00
|
|
|
// Kirdy Design Specs:
|
|
|
|
// MaxV = 5.0V
|
|
|
|
// MAX Current = +- 1.0A
|
2024-04-23 17:09:26 +08:00
|
|
|
const MAX_I_SET: ElectricCurrent = ElectricCurrent {
|
2024-01-18 15:14:24 +08:00
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 1.0,
|
|
|
|
};
|
2024-05-10 12:27:34 +08:00
|
|
|
const MAX_V_DUTY_TO_VOLTAGE_RATE: ElectricPotential = ElectricPotential {
|
2024-01-05 15:00:50 +08:00
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 4.0 * 3.3,
|
|
|
|
};
|
|
|
|
pub const MAX_V_MAX: ElectricPotential = ElectricPotential {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 5.0,
|
|
|
|
};
|
2024-04-23 17:09:26 +08:00
|
|
|
const MAX_V_DUTY_MAX: f64 =
|
2024-05-10 12:27:34 +08:00
|
|
|
TecSettings::MAX_V_MAX.value as f64 / TecSettings::MAX_V_DUTY_TO_VOLTAGE_RATE.value as f64;
|
2024-01-05 15:00:50 +08:00
|
|
|
const MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE: ElectricCurrent = ElectricCurrent {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 1.0 / (10.0 * R_SENSE.value / 3.3),
|
|
|
|
};
|
|
|
|
pub const MAX_I_POS_CURRENT: ElectricCurrent = ElectricCurrent {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 1.0,
|
|
|
|
};
|
|
|
|
pub const MAX_I_NEG_CURRENT: ElectricCurrent = ElectricCurrent {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 1.0,
|
|
|
|
};
|
|
|
|
// .get::<ratio>() is not implemented for const
|
2024-04-23 17:09:26 +08:00
|
|
|
const MAX_I_POS_DUTY_MAX: f64 =
|
|
|
|
TecSettings::MAX_I_POS_CURRENT.value as f64 / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE.value as f64;
|
|
|
|
const MAX_I_NEG_DUTY_MAX: f64 =
|
|
|
|
TecSettings::MAX_I_NEG_CURRENT.value as f64 / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE.value as f64;
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
|
|
|
|
2024-01-18 15:14:24 +08:00
|
|
|
impl Default for TecSettings {
|
2024-01-05 15:00:50 +08:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2024-03-04 15:48:19 +08:00
|
|
|
default_pwr_on: false,
|
2024-01-05 15:00:50 +08:00
|
|
|
center_pt: ElectricPotential::new::<volt>(1.5),
|
|
|
|
max_v_set: ElectricPotential::new::<volt>(5.0),
|
|
|
|
max_i_pos_set: ElectricCurrent::new::<ampere>(1.0),
|
|
|
|
max_i_neg_set: ElectricCurrent::new::<ampere>(1.0),
|
|
|
|
i_set: ElectricCurrent::new::<ampere>(0.0),
|
2024-02-29 16:47:03 +08:00
|
|
|
vref: ElectricPotential::new::<volt>(1.5),
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2024-01-04 17:13:46 +08:00
|
|
|
|
2023-12-21 13:13:06 +08:00
|
|
|
pub struct Thermostat {
|
|
|
|
max1968: MAX1968,
|
2024-01-17 16:06:07 +08:00
|
|
|
ad7172: ad7172::AdcPhy,
|
2024-02-23 11:38:26 +08:00
|
|
|
pub tec_settings: TecSettings,
|
2024-01-17 17:01:25 +08:00
|
|
|
pid_ctrl_ch0: PidState,
|
2024-02-21 15:49:08 +08:00
|
|
|
temp_mon: TempMon,
|
2023-12-21 13:13:06 +08:00
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
|
|
|
pub struct ThermostatSettingsSummary {
|
2024-03-04 15:48:19 +08:00
|
|
|
default_pwr_on: bool,
|
|
|
|
pid_engaged: bool,
|
2024-04-02 15:22:52 +08:00
|
|
|
temperature_setpoint: f32,
|
2024-02-23 11:38:26 +08:00
|
|
|
tec_settings: TecSettingSummary,
|
|
|
|
pid_params: PidParams,
|
2024-03-04 15:48:19 +08:00
|
|
|
temp_adc_settings: TempAdcFilter,
|
2024-02-23 11:38:26 +08:00
|
|
|
temp_mon_settings: TempMonSettings,
|
|
|
|
thermistor_params: ThermistorParams,
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
impl Thermostat {
|
|
|
|
pub fn new(max1968: MAX1968, ad7172: ad7172::AdcPhy) -> Self {
|
|
|
|
Thermostat {
|
2024-01-05 15:00:50 +08:00
|
|
|
max1968: max1968,
|
2024-01-17 16:06:07 +08:00
|
|
|
ad7172: ad7172,
|
2024-02-23 11:38:26 +08:00
|
|
|
tec_settings: TecSettings::default(),
|
2024-01-17 17:01:25 +08:00
|
|
|
pid_ctrl_ch0: PidState::default(),
|
2024-02-21 15:49:08 +08:00
|
|
|
temp_mon: TempMon::default(),
|
2023-12-21 13:13:06 +08:00
|
|
|
}
|
|
|
|
}
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn setup(&mut self) {
|
2024-01-17 16:06:07 +08:00
|
|
|
self.tec_setup();
|
|
|
|
let t_adc_ch0_cal = self.t_adc_setup();
|
2024-04-23 17:09:26 +08:00
|
|
|
self.pid_ctrl_ch0.set_adc_calibration(t_adc_ch0_cal);
|
2024-01-17 16:06:07 +08:00
|
|
|
}
|
|
|
|
|
2024-02-29 16:47:03 +08:00
|
|
|
/// start_tec_readings_conversion() should not be called before the current
|
|
|
|
/// DMA request is serviced or the conversion process will be restarted
|
|
|
|
/// Thus, no new readings is available when you call get_tec_readings() fn
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn start_tec_readings_conversion(&mut self) {
|
2024-02-29 16:47:03 +08:00
|
|
|
self.max1968.dma_adc_start_conversion();
|
|
|
|
}
|
|
|
|
|
2024-01-17 16:06:07 +08:00
|
|
|
fn tec_setup(&mut self) {
|
2024-01-09 16:58:17 +08:00
|
|
|
self.power_down();
|
2024-01-04 17:13:46 +08:00
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
self.tec_settings = TecSettings::default();
|
2024-01-04 17:13:46 +08:00
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
self.set_i(self.tec_settings.i_set);
|
2024-01-04 17:13:46 +08:00
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
self.set_max_v(self.tec_settings.max_v_set);
|
|
|
|
self.set_max_i_pos(self.tec_settings.max_i_pos_set);
|
|
|
|
self.set_max_i_neg(self.tec_settings.max_i_neg_set);
|
2024-01-04 17:13:46 +08:00
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn t_adc_setup(&mut self) -> ad7172::ChannelCalibration {
|
2024-01-17 16:06:07 +08:00
|
|
|
self.ad7172.set_sync_enable(false).unwrap();
|
2024-04-23 17:09:26 +08:00
|
|
|
self.ad7172
|
|
|
|
.setup_channel(0, ad7172::Input::Ain0, ad7172::Input::Ain1)
|
|
|
|
.unwrap();
|
|
|
|
let adc_calibration0 = self.ad7172.get_calibration(0).expect("adc_calibration0");
|
2024-01-17 16:06:07 +08:00
|
|
|
self.ad7172.start_continuous_conversion().unwrap();
|
|
|
|
adc_calibration0
|
|
|
|
}
|
|
|
|
|
2024-02-23 12:58:35 +08:00
|
|
|
pub fn poll_adc(&mut self) -> bool {
|
2024-02-15 11:00:53 +08:00
|
|
|
let mut data_rdy = false;
|
2024-04-23 17:09:26 +08:00
|
|
|
self.ad7172.data_ready().unwrap().map(|_ch| {
|
2024-01-17 16:06:07 +08:00
|
|
|
let data = self.ad7172.read_data().unwrap();
|
2024-01-17 17:01:25 +08:00
|
|
|
let state: &mut PidState = &mut self.pid_ctrl_ch0;
|
2024-02-06 15:20:23 +08:00
|
|
|
state.update(data);
|
2024-02-21 15:49:08 +08:00
|
|
|
let pid_engaged = state.get_pid_engaged();
|
|
|
|
let temp = self.get_temperature();
|
2024-04-23 17:09:26 +08:00
|
|
|
self.temp_mon
|
|
|
|
.update_status(pid_engaged, self.max1968.is_powered_on(), temp);
|
2024-02-23 12:58:35 +08:00
|
|
|
debug!("state.get_pid_engaged(): {:?}", pid_engaged);
|
2024-02-21 15:49:08 +08:00
|
|
|
debug!("Temperature: {:?} degree", temp.get::<degree_celsius>());
|
2024-02-15 11:00:53 +08:00
|
|
|
data_rdy = true;
|
2024-02-06 15:20:23 +08:00
|
|
|
});
|
2024-02-15 11:00:53 +08:00
|
|
|
data_rdy
|
2024-01-17 16:06:07 +08:00
|
|
|
}
|
|
|
|
|
2024-02-23 12:58:35 +08:00
|
|
|
pub fn update_pid(&mut self) {
|
|
|
|
let state: &mut PidState = &mut self.pid_ctrl_ch0;
|
|
|
|
let pid_engaged = state.get_pid_engaged();
|
|
|
|
if pid_engaged {
|
|
|
|
match state.update_pid() {
|
|
|
|
Some(pid_output) => {
|
2024-02-27 16:26:05 +08:00
|
|
|
self.set_i(ElectricCurrent::new::<ampere>(pid_output as f32));
|
2024-04-23 17:09:26 +08:00
|
|
|
debug!(
|
|
|
|
"Temperature Set Point: {:?} degree",
|
|
|
|
self.pid_ctrl_ch0.get_pid_setpoint().get::<degree_celsius>()
|
|
|
|
);
|
2024-02-23 12:58:35 +08:00
|
|
|
}
|
2024-04-23 17:09:26 +08:00
|
|
|
None => {}
|
2024-02-23 12:58:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-21 15:49:08 +08:00
|
|
|
pub fn get_temp_mon_status(&mut self) -> TempStatus {
|
|
|
|
self.temp_mon.get_status()
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn power_up(&mut self) {
|
2024-01-04 17:13:46 +08:00
|
|
|
self.max1968.power_up();
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn power_down(&mut self) {
|
2024-01-04 17:13:46 +08:00
|
|
|
self.max1968.power_down();
|
2024-02-06 15:20:23 +08:00
|
|
|
self.pid_ctrl_ch0.reset_pid_state();
|
|
|
|
self.set_i(ElectricCurrent::new::<ampere>(0.0));
|
2024-01-04 17:13:46 +08:00
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn set_center_pt(&mut self, value: ElectricPotential) {
|
2024-02-23 11:38:26 +08:00
|
|
|
self.tec_settings.center_pt = value;
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
|
|
|
|
2024-03-04 15:48:19 +08:00
|
|
|
pub fn set_default_pwr_on(&mut self, pwr_on: bool) {
|
2024-04-23 17:09:26 +08:00
|
|
|
self.tec_settings.default_pwr_on = pwr_on;
|
2024-03-04 15:48:19 +08:00
|
|
|
}
|
|
|
|
|
2024-01-04 17:13:46 +08:00
|
|
|
pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent {
|
2024-02-23 11:38:26 +08:00
|
|
|
let voltage = i_tec * 10.0 * R_SENSE + self.tec_settings.center_pt;
|
2024-01-18 15:14:24 +08:00
|
|
|
let voltage = self.max1968.set_dac(voltage, TecSettings::DAC_OUT_V_MAX);
|
2024-02-23 11:38:26 +08:00
|
|
|
self.tec_settings.i_set = (voltage - self.tec_settings.center_pt) / (10.0 * R_SENSE);
|
|
|
|
self.tec_settings.i_set
|
2024-01-04 17:13:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_v(&mut self, max_v: ElectricPotential) -> ElectricPotential {
|
2024-05-10 12:27:34 +08:00
|
|
|
let duty = (max_v / TecSettings::MAX_V_DUTY_TO_VOLTAGE_RATE).get::<ratio>();
|
2024-04-23 17:09:26 +08:00
|
|
|
let duty = self
|
|
|
|
.max1968
|
|
|
|
.set_pwm(PwmPinsEnum::MaxV, duty as f64, TecSettings::MAX_V_DUTY_MAX);
|
2024-05-10 12:27:34 +08:00
|
|
|
self.tec_settings.max_v_set = duty as f32 * TecSettings::MAX_V_DUTY_TO_VOLTAGE_RATE;
|
2024-02-23 11:38:26 +08:00
|
|
|
self.tec_settings.max_v_set
|
2024-01-04 17:13:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_i_pos(&mut self, max_i_pos: ElectricCurrent) -> ElectricCurrent {
|
2024-01-18 15:14:24 +08:00
|
|
|
let duty = (max_i_pos / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE).get::<ratio>();
|
2024-04-23 17:09:26 +08:00
|
|
|
let duty = self
|
|
|
|
.max1968
|
|
|
|
.set_pwm(PwmPinsEnum::MaxPosI, duty as f64, TecSettings::MAX_I_POS_DUTY_MAX);
|
2024-02-27 16:26:05 +08:00
|
|
|
self.tec_settings.max_i_pos_set = duty as f32 * TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE;
|
2024-02-23 11:38:26 +08:00
|
|
|
self.tec_settings.max_i_pos_set
|
2024-01-04 17:13:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_i_neg(&mut self, max_i_neg: ElectricCurrent) -> ElectricCurrent {
|
2024-01-18 15:14:24 +08:00
|
|
|
let duty = (max_i_neg / TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE).get::<ratio>();
|
2024-04-23 17:09:26 +08:00
|
|
|
let duty = self
|
|
|
|
.max1968
|
|
|
|
.set_pwm(PwmPinsEnum::MaxNegI, duty as f64, TecSettings::MAX_I_NEG_DUTY_MAX);
|
2024-02-27 16:26:05 +08:00
|
|
|
self.tec_settings.max_i_neg_set = duty as f32 * TecSettings::MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE;
|
2024-02-23 11:38:26 +08:00
|
|
|
self.tec_settings.max_i_neg_set
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
|
|
|
|
2024-02-29 16:47:03 +08:00
|
|
|
#[allow(unused)]
|
|
|
|
fn get_dac_vfb(&mut self) -> ElectricPotential {
|
|
|
|
self.max1968.adc_read(AdcReadTarget::DacVfb, 16)
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
|
|
|
|
2024-02-29 16:47:03 +08:00
|
|
|
#[allow(unused)]
|
|
|
|
fn get_vref(&mut self) -> ElectricPotential {
|
|
|
|
self.max1968.adc_read(AdcReadTarget::VREF, 16)
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
|
|
|
|
2024-02-29 16:47:03 +08:00
|
|
|
#[allow(unused)]
|
2024-01-05 15:00:50 +08:00
|
|
|
pub fn get_tec_i(&mut self) -> ElectricCurrent {
|
2024-02-29 16:47:03 +08:00
|
|
|
let vref = self.max1968.adc_read(AdcReadTarget::VREF, 16);
|
|
|
|
(self.max1968.adc_read(AdcReadTarget::ITec, 16) - vref) / ElectricalResistance::new::<ohm>(0.4)
|
2024-01-05 15:00:50 +08:00
|
|
|
}
|
|
|
|
|
2024-02-29 16:47:03 +08:00
|
|
|
#[allow(unused)]
|
2024-01-05 15:00:50 +08:00
|
|
|
pub fn get_tec_v(&mut self) -> ElectricPotential {
|
2024-02-29 16:47:03 +08:00
|
|
|
(self.max1968.adc_read(AdcReadTarget::VTec, 16) - TecSettings::TEC_VSEC_BIAS_V) * 4.0
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_tec_readings(&mut self) -> (ElectricPotential, ElectricCurrent) {
|
|
|
|
let vref = self.tec_settings.vref;
|
|
|
|
let (vtec, itec) = self.max1968.get_tec_readings();
|
2024-04-23 17:09:26 +08:00
|
|
|
(
|
|
|
|
(vtec - TecSettings::TEC_VSEC_BIAS_V) * 4.0,
|
|
|
|
(itec - vref) / ElectricalResistance::new::<ohm>(0.4),
|
|
|
|
)
|
2023-12-21 13:13:06 +08:00
|
|
|
}
|
2023-12-22 17:09:45 +08:00
|
|
|
|
|
|
|
/// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output.
|
|
|
|
///
|
|
|
|
/// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
|
|
|
|
/// The CTLI input signal is centered around VREF of the MAX chip. Applying VREF to CTLI sets the output current to 0.
|
2024-04-23 17:09:26 +08:00
|
|
|
///
|
2023-12-22 17:09:45 +08:00
|
|
|
/// This calibration routine measures the VREF voltage and the DAC output with the STM32 ADC, and uses a breadth-first
|
2024-04-23 17:09:26 +08:00
|
|
|
/// search to find the DAC setting that will produce a DAC output voltage closest to VREF. This DAC output voltage will
|
|
|
|
/// be stored and used in subsequent i_set routines to bias the current control signal to the measured VREF, reducing
|
2023-12-22 17:09:45 +08:00
|
|
|
/// the offset error of the current control signal.
|
|
|
|
///
|
|
|
|
/// The input offset of the STM32 ADC is eliminated by using the same ADC for the measurements, and by only using the
|
|
|
|
/// difference in VREF and DAC output for the calibration.
|
2024-04-23 17:09:26 +08:00
|
|
|
///
|
|
|
|
/// This routine should be called only once after boot, repeated reading of the vref signal and changing of the stored
|
2023-12-22 17:09:45 +08:00
|
|
|
/// VREF measurement can introduce significant noise at the current output, degrading the stabilily performance of the
|
2024-04-23 17:09:26 +08:00
|
|
|
/// thermostat.
|
2023-12-22 17:09:45 +08:00
|
|
|
pub fn calibrate_dac_value(&mut self) {
|
2024-04-23 15:42:17 +08:00
|
|
|
const DAC_BIT: u32 = 18;
|
|
|
|
const ADC_BIT: u32 = 12;
|
|
|
|
let target_voltage = self.max1968.adc_read(AdcReadTarget::VREF, 512);
|
2023-12-22 17:09:45 +08:00
|
|
|
let mut start_value = 1;
|
|
|
|
let mut best_error = ElectricPotential::new::<volt>(100.0);
|
2024-04-23 17:09:26 +08:00
|
|
|
for step in (DAC_BIT - ADC_BIT - 1..DAC_BIT).rev() {
|
2023-12-22 17:09:45 +08:00
|
|
|
let mut prev_value = start_value;
|
|
|
|
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
|
|
|
|
self.max1968.phy.dac.set(value).unwrap();
|
|
|
|
sys_timer::sleep(5);
|
|
|
|
|
|
|
|
let dac_feedback = self.max1968.adc_read(AdcReadTarget::DacVfb, 64);
|
|
|
|
let error = target_voltage - dac_feedback;
|
|
|
|
if error < ElectricPotential::new::<volt>(0.0) {
|
|
|
|
break;
|
|
|
|
} else if error < best_error {
|
|
|
|
best_error = error;
|
|
|
|
start_value = prev_value;
|
|
|
|
|
2024-02-27 16:26:05 +08:00
|
|
|
let vref = (value as f32 / ad5680::MAX_VALUE as f32) * TecSettings::DAC_OUT_V_MAX;
|
2024-01-05 15:00:50 +08:00
|
|
|
self.set_center_pt(vref);
|
2023-12-22 17:09:45 +08:00
|
|
|
}
|
|
|
|
prev_value = value;
|
|
|
|
}
|
|
|
|
}
|
2024-02-29 16:47:03 +08:00
|
|
|
self.tec_settings.vref = target_voltage;
|
2023-12-22 17:09:45 +08:00
|
|
|
}
|
2024-04-23 17:09:26 +08:00
|
|
|
|
2024-02-06 15:20:23 +08:00
|
|
|
pub fn set_pid_engaged(&mut self, val: bool) {
|
|
|
|
self.pid_ctrl_ch0.set_pid_engaged(val);
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
fn get_pid_engaged(&mut self) -> bool {
|
2024-04-23 17:09:26 +08:00
|
|
|
self.pid_ctrl_ch0.get_pid_engaged()
|
2024-01-17 16:06:07 +08:00
|
|
|
}
|
|
|
|
|
2024-01-18 15:14:24 +08:00
|
|
|
pub fn get_status_report(&mut self) -> StatusReport {
|
2024-02-29 16:47:03 +08:00
|
|
|
let (tec_v, tec_i) = self.get_tec_readings();
|
2024-03-18 15:41:31 +08:00
|
|
|
let temperature: Option<f32>;
|
|
|
|
|
|
|
|
match self.pid_ctrl_ch0.get_temperature() {
|
2024-04-23 17:09:26 +08:00
|
|
|
Some(val) => temperature = Some(val.get::<degree_celsius>()),
|
2024-03-18 15:41:31 +08:00
|
|
|
None => {
|
|
|
|
temperature = None;
|
|
|
|
}
|
|
|
|
}
|
2024-04-23 17:09:26 +08:00
|
|
|
|
2024-01-18 15:14:24 +08:00
|
|
|
StatusReport {
|
2024-03-04 15:48:19 +08:00
|
|
|
pwr_on: self.max1968.is_powered_on(),
|
2024-02-06 15:20:23 +08:00
|
|
|
pid_engaged: self.get_pid_engaged(),
|
2024-02-21 15:49:08 +08:00
|
|
|
temp_mon_status: self.temp_mon.get_status(),
|
2024-03-18 15:41:31 +08:00
|
|
|
temperature: temperature,
|
2024-02-23 11:38:26 +08:00
|
|
|
i_set: self.tec_settings.i_set,
|
2024-02-29 16:47:03 +08:00
|
|
|
tec_i: tec_i,
|
|
|
|
tec_v: tec_v,
|
2024-01-18 15:14:24 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-06 15:20:23 +08:00
|
|
|
pub fn get_temperature(&mut self) -> ThermodynamicTemperature {
|
|
|
|
match self.pid_ctrl_ch0.get_temperature() {
|
2024-04-23 17:09:26 +08:00
|
|
|
Some(val) => val,
|
|
|
|
None => ThermodynamicTemperature::new::<degree_celsius>(NAN),
|
2024-02-06 15:20:23 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
fn get_pid_settings(&mut self) -> pid_state::Parameters {
|
2024-02-06 15:20:23 +08:00
|
|
|
self.pid_ctrl_ch0.get_pid_settings()
|
2024-01-18 15:14:24 +08:00
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
fn get_steinhart_hart(&mut self) -> ThermistorParams {
|
|
|
|
let sh = self.pid_ctrl_ch0.get_sh();
|
|
|
|
ThermistorParams {
|
|
|
|
t0: sh.t0.get::<degree_celsius>(),
|
|
|
|
r0: sh.r0,
|
|
|
|
b: sh.b,
|
|
|
|
}
|
2024-01-18 15:14:24 +08:00
|
|
|
}
|
|
|
|
|
2024-03-04 15:48:19 +08:00
|
|
|
fn apply_steinhart_hart(&mut self, sh: ThermistorParams) {
|
2024-04-23 17:09:26 +08:00
|
|
|
self.pid_ctrl_ch0.apply_sh(Sh_Params {
|
|
|
|
t0: ThermodynamicTemperature::new::<degree_celsius>(sh.t0),
|
|
|
|
r0: sh.r0,
|
|
|
|
b: sh.b,
|
|
|
|
})
|
2024-03-04 15:48:19 +08:00
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
fn get_tec_settings(&mut self) -> TecSettingSummary {
|
2024-01-18 15:14:24 +08:00
|
|
|
TecSettingSummary {
|
2024-04-23 17:09:26 +08:00
|
|
|
i_set: TecSettingsSummaryField {
|
|
|
|
value: self.tec_settings.i_set,
|
|
|
|
max: TecSettings::MAX_I_SET,
|
|
|
|
},
|
|
|
|
max_v: TecSettingsSummaryField {
|
|
|
|
value: self.tec_settings.max_v_set,
|
|
|
|
max: TecSettings::MAX_V_MAX,
|
|
|
|
},
|
|
|
|
max_i_pos: TecSettingsSummaryField {
|
|
|
|
value: self.tec_settings.max_i_pos_set,
|
|
|
|
max: TecSettings::MAX_I_POS_CURRENT,
|
|
|
|
},
|
|
|
|
max_i_neg: TecSettingsSummaryField {
|
|
|
|
value: self.tec_settings.max_i_neg_set,
|
|
|
|
max: TecSettings::MAX_I_NEG_CURRENT,
|
|
|
|
},
|
2024-01-18 15:14:24 +08:00
|
|
|
}
|
|
|
|
}
|
2024-01-22 12:24:19 +08:00
|
|
|
|
|
|
|
pub fn get_calibrated_vdda(&mut self) -> u32 {
|
|
|
|
self.max1968.get_calibrated_vdda()
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn set_pid(&mut self, param: PidSettings, val: f32) {
|
2024-02-06 15:20:23 +08:00
|
|
|
self.pid_ctrl_ch0.set_pid_params(param, val);
|
|
|
|
}
|
|
|
|
|
2024-02-27 16:26:05 +08:00
|
|
|
pub fn set_sh_beta(&mut self, beta: f32) {
|
2024-02-06 15:20:23 +08:00
|
|
|
self.pid_ctrl_ch0.set_sh_beta(beta);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_sh_r0(&mut self, r0: ElectricalResistance) {
|
|
|
|
self.pid_ctrl_ch0.set_sh_r0(r0);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_sh_t0(&mut self, t0: ThermodynamicTemperature) {
|
|
|
|
self.pid_ctrl_ch0.set_sh_t0(t0);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_temperature_setpoint(&mut self, t: ThermodynamicTemperature) {
|
2024-04-23 17:09:26 +08:00
|
|
|
let t = t
|
|
|
|
.min(self.temp_mon.get_upper_limit())
|
|
|
|
.max(self.temp_mon.get_lower_limit());
|
2024-02-06 15:20:23 +08:00
|
|
|
self.pid_ctrl_ch0.set_pid_setpoint(t);
|
2024-02-21 15:49:08 +08:00
|
|
|
self.temp_mon.set_setpoint(t);
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn apply_temp_mon_settings(&mut self, settings: TempMonSettings) {
|
|
|
|
self.temp_mon
|
|
|
|
.set_upper_limit(ThermodynamicTemperature::new::<degree_celsius>(settings.upper_limit));
|
|
|
|
self.temp_mon
|
|
|
|
.set_lower_limit(ThermodynamicTemperature::new::<degree_celsius>(settings.lower_limit));
|
2024-03-04 15:48:19 +08:00
|
|
|
}
|
|
|
|
|
2024-02-21 15:49:08 +08:00
|
|
|
pub fn set_temp_mon_upper_limit(&mut self, t: ThermodynamicTemperature) {
|
|
|
|
self.temp_mon.set_upper_limit(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_temp_mon_lower_limit(&mut self, t: ThermodynamicTemperature) {
|
|
|
|
self.temp_mon.set_lower_limit(t);
|
2024-02-06 15:20:23 +08:00
|
|
|
}
|
2024-02-21 15:49:08 +08:00
|
|
|
|
2024-02-26 15:22:15 +08:00
|
|
|
pub fn set_temp_adc_sinc5_sinc1_filter(&mut self, index: u8, odr: ad7172::SingleChODR) {
|
|
|
|
self.ad7172.set_sinc5_sinc1_filter(index, odr).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_temp_adc_sinc3_filter(&mut self, index: u8, odr: ad7172::SingleChODR) {
|
|
|
|
self.ad7172.set_sinc3_filter(index, odr).unwrap();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_temp_adc_sinc5_sinc1_with_postfilter(&mut self, index: u8, odr: ad7172::PostFilter) {
|
2024-04-23 17:09:26 +08:00
|
|
|
self.ad7172
|
|
|
|
.set_sinc5_sinc1_with_50hz_60hz_rejection(index, odr)
|
|
|
|
.unwrap();
|
2024-02-26 15:22:15 +08:00
|
|
|
}
|
|
|
|
|
2024-03-04 15:48:19 +08:00
|
|
|
pub fn set_temp_adc_sinc3_fine_filter(&mut self, index: u8, rate: f32) {
|
|
|
|
self.ad7172.set_sinc3_fine_filter(index, rate).unwrap();
|
2024-02-26 15:22:15 +08:00
|
|
|
}
|
|
|
|
|
2024-02-21 15:49:08 +08:00
|
|
|
pub fn clear_temp_mon_alarm(&mut self) {
|
|
|
|
self.temp_mon.clear_alarm();
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
fn get_temp_mon_settings(&mut self) -> TempMonSettings {
|
2024-02-21 15:49:08 +08:00
|
|
|
self.temp_mon.get_settings()
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
pub fn get_settings_summary(&mut self) -> ThermostatSettingsSummary {
|
2024-03-04 15:48:19 +08:00
|
|
|
let temp_adc_filter_type: FilterType;
|
|
|
|
let update_rate: f32;
|
|
|
|
match self.ad7172.get_filter_type_and_rate(0) {
|
|
|
|
Ok((filter_type, rate)) => {
|
|
|
|
temp_adc_filter_type = filter_type;
|
|
|
|
update_rate = rate;
|
|
|
|
}
|
|
|
|
Err(_) => {
|
|
|
|
panic!("Cannot read ADC filter type and rate");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
ThermostatSettingsSummary {
|
2024-03-04 15:48:19 +08:00
|
|
|
default_pwr_on: self.tec_settings.default_pwr_on,
|
|
|
|
pid_engaged: self.get_pid_engaged(),
|
2024-04-02 15:22:52 +08:00
|
|
|
temperature_setpoint: self.pid_ctrl_ch0.get_pid_setpoint().get::<degree_celsius>(),
|
2024-02-23 11:38:26 +08:00
|
|
|
tec_settings: self.get_tec_settings(),
|
|
|
|
pid_params: self.get_pid_settings(),
|
2024-04-23 17:09:26 +08:00
|
|
|
temp_adc_settings: TempAdcFilter {
|
|
|
|
filter_type: temp_adc_filter_type,
|
|
|
|
sinc5sinc1odr: None,
|
|
|
|
sinc3odr: None,
|
|
|
|
sinc5sinc1postfilter: None,
|
|
|
|
sinc3fineodr: None,
|
|
|
|
rate: Some(update_rate),
|
2024-03-04 15:48:19 +08:00
|
|
|
},
|
2024-02-23 11:38:26 +08:00
|
|
|
temp_mon_settings: self.get_temp_mon_settings(),
|
|
|
|
thermistor_params: self.get_steinhart_hart(),
|
|
|
|
}
|
|
|
|
}
|
2024-03-04 15:50:30 +08:00
|
|
|
|
|
|
|
pub fn load_settings_from_summary(&mut self, settings: ThermostatSettingsSummary) {
|
|
|
|
self.power_down();
|
|
|
|
self.set_max_i_neg(settings.tec_settings.max_i_neg.value);
|
|
|
|
self.set_max_i_pos(settings.tec_settings.max_i_pos.value);
|
|
|
|
self.set_max_v(settings.tec_settings.max_v.value);
|
2024-04-23 17:09:26 +08:00
|
|
|
|
2024-03-04 15:50:30 +08:00
|
|
|
self.apply_steinhart_hart(settings.thermistor_params);
|
|
|
|
self.apply_temp_mon_settings(settings.temp_mon_settings);
|
2024-04-23 17:09:26 +08:00
|
|
|
|
2024-03-04 15:50:30 +08:00
|
|
|
match settings.temp_adc_settings.rate {
|
2024-04-23 17:09:26 +08:00
|
|
|
Some(rate) => match settings.temp_adc_settings.filter_type {
|
|
|
|
FilterType::Sinc3 => self.set_temp_adc_sinc3_filter(0, SingleChODR::closest(rate).unwrap()),
|
|
|
|
FilterType::Sinc5Sinc1 => self.set_temp_adc_sinc5_sinc1_filter(0, SingleChODR::closest(rate).unwrap()),
|
|
|
|
FilterType::Sinc3WithFineODR => self.set_temp_adc_sinc3_fine_filter(0, rate),
|
|
|
|
FilterType::Sinc5Sinc1With50hz60HzRejection => {
|
|
|
|
self.set_temp_adc_sinc5_sinc1_with_postfilter(0, PostFilter::closest(rate).unwrap())
|
2024-03-04 15:50:30 +08:00
|
|
|
}
|
2024-04-23 17:09:26 +08:00
|
|
|
},
|
2024-03-04 15:50:30 +08:00
|
|
|
None => {
|
|
|
|
debug!(" Temperature ADC Settings is not found");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.set_pid_engaged(settings.pid_engaged);
|
|
|
|
self.pid_ctrl_ch0.apply_pid_params(settings.pid_params);
|
2024-04-23 17:09:26 +08:00
|
|
|
self.set_temperature_setpoint(ThermodynamicTemperature::new::<degree_celsius>(
|
|
|
|
settings.temperature_setpoint,
|
|
|
|
));
|
2024-03-04 15:50:30 +08:00
|
|
|
if !settings.pid_engaged {
|
|
|
|
self.set_i(settings.tec_settings.i_set.value);
|
|
|
|
}
|
2024-04-09 11:42:04 +08:00
|
|
|
self.set_default_pwr_on(settings.default_pwr_on);
|
2024-03-04 15:50:30 +08:00
|
|
|
if settings.default_pwr_on {
|
|
|
|
self.power_up();
|
|
|
|
} else {
|
|
|
|
self.power_down();
|
|
|
|
}
|
|
|
|
}
|
2024-01-18 15:14:24 +08:00
|
|
|
}
|
|
|
|
|
2024-02-19 15:08:00 +08:00
|
|
|
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
2024-01-18 15:14:24 +08:00
|
|
|
pub struct StatusReport {
|
2024-03-04 15:48:19 +08:00
|
|
|
pwr_on: bool,
|
2024-01-18 15:14:24 +08:00
|
|
|
pid_engaged: bool,
|
2024-02-21 15:49:08 +08:00
|
|
|
temp_mon_status: TempStatus,
|
2024-03-18 15:41:31 +08:00
|
|
|
temperature: Option<f32>,
|
2024-01-18 15:14:24 +08:00
|
|
|
i_set: ElectricCurrent,
|
|
|
|
tec_i: ElectricCurrent,
|
|
|
|
tec_v: ElectricPotential,
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
2024-01-18 15:14:24 +08:00
|
|
|
pub struct TecSettingsSummaryField<T> {
|
|
|
|
value: T,
|
|
|
|
max: T,
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
#[derive(Deserialize, Serialize, Copy, Clone, Debug, Tree)]
|
2024-01-18 15:14:24 +08:00
|
|
|
pub struct TecSettingSummary {
|
|
|
|
i_set: TecSettingsSummaryField<ElectricCurrent>,
|
|
|
|
max_v: TecSettingsSummaryField<ElectricPotential>,
|
|
|
|
max_i_pos: TecSettingsSummaryField<ElectricCurrent>,
|
|
|
|
max_i_neg: TecSettingsSummaryField<ElectricCurrent>,
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:38:26 +08:00
|
|
|
#[derive(Deserialize, Serialize, Clone, Copy, Debug, Tree)]
|
|
|
|
pub struct ThermistorParams {
|
2024-02-27 16:26:05 +08:00
|
|
|
t0: f32,
|
2024-02-23 11:38:26 +08:00
|
|
|
r0: ElectricalResistance,
|
2024-04-23 17:09:26 +08:00
|
|
|
b: f32,
|
2024-01-17 16:06:07 +08:00
|
|
|
}
|