2024-01-04 17:13:46 +08:00
|
|
|
use core::marker::PhantomData;
|
2023-12-22 17:09:45 +08:00
|
|
|
use crate::sys_timer;
|
2023-12-21 13:13:06 +08:00
|
|
|
use crate::thermostat::ad5680;
|
2024-01-04 17:13:46 +08:00
|
|
|
use crate::thermostat::max1968::{MAX1968, AdcReadTarget, PwmPinsEnum};
|
2023-12-22 17:09:45 +08:00
|
|
|
use log::info;
|
|
|
|
use uom::si::{
|
|
|
|
electric_current::ampere,
|
|
|
|
electric_potential::{millivolt, volt},
|
|
|
|
electrical_resistance::ohm,
|
|
|
|
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance},
|
|
|
|
ratio::ratio,
|
|
|
|
};
|
2023-12-21 13:13:06 +08:00
|
|
|
|
2024-01-04 17:13:46 +08:00
|
|
|
pub const R_SENSE: ElectricalResistance = ElectricalResistance {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 0.05,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Rev 0_2: DAC Chip connects 3V3 reference voltage and thus provide 0-3.3V output range
|
|
|
|
// TODO: Rev 0_3: DAC Chip connects 3V3 reference voltage,
|
|
|
|
// which is then passed through a resistor divider to provide 0-3V output range
|
|
|
|
pub const DAC_OUT_V_MAX: f64 = 3.3;
|
|
|
|
pub const TEC_VSEC_BIAS_V: ElectricPotential = ElectricPotential {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 1.65,
|
|
|
|
};
|
|
|
|
|
|
|
|
// Kirdy Design Specs:
|
|
|
|
// MaxV = 5.0V
|
|
|
|
// MAX Current = +- 1.0A
|
|
|
|
const MAX_V_DUTY_TO_CURRENT_RATE: ElectricPotential = ElectricPotential {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 4.0 * 3.3,
|
|
|
|
};
|
|
|
|
pub const MAX_V_MAX: ElectricPotential = ElectricPotential {
|
|
|
|
dimension: PhantomData,
|
|
|
|
units: PhantomData,
|
|
|
|
value: 5.0,
|
|
|
|
};
|
|
|
|
const MAX_V_DUTY_MAX: f64 = MAX_V_MAX.value / MAX_V_DUTY_TO_CURRENT_RATE.value;
|
|
|
|
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
|
|
|
|
const MAX_I_POS_DUTY_MAX: f64 = MAX_I_POS_CURRENT.value / MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE.value;
|
|
|
|
const MAX_I_NEG_DUTY_MAX: f64 = MAX_I_NEG_CURRENT.value / MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE.value;
|
|
|
|
|
2023-12-21 13:13:06 +08:00
|
|
|
pub struct Thermostat {
|
|
|
|
max1968: MAX1968,
|
|
|
|
// TADC
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Thermostat{
|
|
|
|
pub fn new (max1968: MAX1968) -> Self {
|
|
|
|
Thermostat{
|
|
|
|
max1968
|
|
|
|
}
|
|
|
|
}
|
2024-01-04 17:13:46 +08:00
|
|
|
pub fn setup(&mut self) {
|
|
|
|
self.power_down();
|
|
|
|
|
|
|
|
self.calibrate_dac_value();
|
|
|
|
|
|
|
|
self.set_i(ElectricCurrent::new::<ampere>(0.0));
|
|
|
|
|
|
|
|
self.set_max_v(ElectricPotential::new::<volt>(5.0));
|
|
|
|
self.set_max_i_pos(ElectricCurrent::new::<ampere>(1.0));
|
|
|
|
self.set_max_i_neg(ElectricCurrent::new::<ampere>(1.0));
|
|
|
|
|
|
|
|
self.max1968.power_up();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn power_up(&mut self){
|
|
|
|
self.max1968.power_up();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn power_down(&mut self){
|
|
|
|
self.max1968.power_down();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent {
|
|
|
|
let voltage = i_tec * 10.0 * R_SENSE + self.max1968.phy.center_pt;
|
|
|
|
let voltage = self.max1968.set_dac(voltage);
|
|
|
|
let i_tec = (voltage - self.max1968.phy.center_pt) / (10.0 * R_SENSE);
|
|
|
|
i_tec
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_v(&mut self, max_v: ElectricPotential) -> ElectricPotential {
|
|
|
|
let duty = (max_v / MAX_V_DUTY_TO_CURRENT_RATE).get::<ratio>();
|
|
|
|
let duty = self.max1968.set_pwm(PwmPinsEnum::MaxV, duty, MAX_V_DUTY_MAX);
|
|
|
|
duty * MAX_V_DUTY_TO_CURRENT_RATE
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_i_pos(&mut self, max_i_pos: ElectricCurrent) -> ElectricCurrent {
|
|
|
|
let duty = (max_i_pos / MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE).get::<ratio>();
|
|
|
|
let duty = self.max1968.set_pwm(PwmPinsEnum::MaxPosI, duty, MAX_I_POS_DUTY_MAX);
|
|
|
|
duty * MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_max_i_neg(&mut self, max_i_neg: ElectricCurrent) -> ElectricCurrent {
|
|
|
|
let duty = (max_i_neg / MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE).get::<ratio>();
|
|
|
|
let duty = self.max1968.set_pwm(PwmPinsEnum::MaxNegI, duty, MAX_I_NEG_DUTY_MAX);
|
|
|
|
duty * MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE
|
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.
|
|
|
|
///
|
|
|
|
/// This calibration routine measures the VREF voltage and the DAC output with the STM32 ADC, and uses a breadth-first
|
|
|
|
/// 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
|
|
|
|
/// 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.
|
|
|
|
///
|
|
|
|
/// This routine should be called only once after boot, repeated reading of the vref signal and changing of the stored
|
|
|
|
/// VREF measurement can introduce significant noise at the current output, degrading the stabilily performance of the
|
|
|
|
/// thermostat.
|
|
|
|
pub fn calibrate_dac_value(&mut self) {
|
|
|
|
let samples = 50;
|
|
|
|
let mut target_voltage = ElectricPotential::new::<volt>(0.0);
|
|
|
|
for _ in 0..samples {
|
|
|
|
target_voltage = target_voltage + self.max1968.adc_read(AdcReadTarget::VREF, 1);
|
|
|
|
}
|
|
|
|
target_voltage = target_voltage / samples as f64;
|
|
|
|
let mut start_value = 1;
|
|
|
|
let mut best_error = ElectricPotential::new::<volt>(100.0);
|
|
|
|
for step in (0..18).rev() {
|
|
|
|
info!("Step: {} Calibrating", step);
|
|
|
|
let mut prev_value = start_value;
|
|
|
|
for value in (start_value..=ad5680::MAX_VALUE).step_by(1 << step) {
|
|
|
|
//info!("Calibrating");
|
|
|
|
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;
|
|
|
|
|
|
|
|
let vref = (value as f64 / ad5680::MAX_VALUE as f64) * ElectricPotential::new::<volt>(DAC_OUT_V_MAX);
|
|
|
|
self.max1968.set_center_point(vref);
|
|
|
|
}
|
|
|
|
prev_value = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-21 13:13:06 +08:00
|
|
|
}
|