use core::marker::PhantomData; use crate::sys_timer; use crate::thermostat::ad5680; use crate::thermostat::max1968::{MAX1968, AdcReadTarget, PwmPinsEnum}; use log::info; use uom::si::{ electric_current::ampere, electric_potential::{millivolt, volt}, electrical_resistance::ohm, f64::{ElectricCurrent, ElectricPotential, ElectricalResistance}, ratio::ratio, }; 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::() 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; pub struct Thermostat { max1968: MAX1968, // TADC } impl Thermostat{ pub fn new (max1968: MAX1968) -> Self { Thermostat{ max1968 } } pub fn setup(&mut self) { self.power_down(); self.calibrate_dac_value(); self.set_i(ElectricCurrent::new::(0.0)); self.set_max_v(ElectricPotential::new::(5.0)); self.set_max_i_pos(ElectricCurrent::new::(1.0)); self.set_max_i_neg(ElectricCurrent::new::(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::(); 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::(); 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::(); let duty = self.max1968.set_pwm(PwmPinsEnum::MaxNegI, duty, MAX_I_NEG_DUTY_MAX); duty * MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE } /// 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::(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::(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::(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::(DAC_OUT_V_MAX); self.max1968.set_center_point(vref); } prev_value = value; } } } }