From 23ee568ea7c6a32ae61f2b390ceb040509ad19f6 Mon Sep 17 00:00:00 2001 From: linuswck Date: Fri, 22 Dec 2023 17:09:45 +0800 Subject: [PATCH] Port TEC DAC calibration procedure from Thermostat - Needs clean up - To be evaluated and rethink the calibration procedure --- src/thermostat/max1968.rs | 12 +++--- src/thermostat/mod.rs | 1 + src/thermostat/thermostat.rs | 72 +++++++++++++++++++++++++++++++++++- 3 files changed, 77 insertions(+), 8 deletions(-) diff --git a/src/thermostat/max1968.rs b/src/thermostat/max1968.rs index e84242a..92c6ccd 100644 --- a/src/thermostat/max1968.rs +++ b/src/thermostat/max1968.rs @@ -35,7 +35,7 @@ pub const R_SENSE: ElectricalResistance = ElectricalResistance { // 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 -const DAC_OUT_V_MAX: f64 = 3.3; +pub const DAC_OUT_V_MAX: f64 = 3.3; const TEC_VSEC_BIAS_V: ElectricPotential = ElectricPotential { dimension: PhantomData, units: PhantomData, @@ -258,17 +258,15 @@ impl MAX1968 { } pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent { - let center_point = self.phy.center_pt; - let r_sense = R_SENSE; - let voltage = i_tec * 10.0 * r_sense + center_point; + let voltage = i_tec * 10.0 * R_SENSE + self.phy.center_pt; let voltage = self.set_dac(voltage); - let i_tec = (voltage - center_point) / (10.0 * r_sense); + let i_tec = (voltage - self.phy.center_pt) / (10.0 * R_SENSE); i_tec } // AN4073: ADC Reading Dispersion can be reduced through Averaging // Upon test, 16 Point Averaging = +-3 LSB Dispersion - fn adc_read(&mut self, adc_read_target: AdcReadTarget, avg_pt: u16) -> ElectricPotential { + pub fn adc_read(&mut self, adc_read_target: AdcReadTarget, avg_pt: u16) -> ElectricPotential { let mut sample: u32 = 0; sample = match adc_read_target { AdcReadTarget::VREF => { @@ -317,7 +315,7 @@ impl MAX1968 { } pub fn get_dac_vfb(&mut self) -> ElectricPotential { - self.adc_read(AdcReadTarget:: DacVfb, 1) + self.adc_read(AdcReadTarget:: DacVfb, 16) } pub fn get_tec_i(&mut self) -> ElectricCurrent { diff --git a/src/thermostat/mod.rs b/src/thermostat/mod.rs index 5137637..50cbd16 100644 --- a/src/thermostat/mod.rs +++ b/src/thermostat/mod.rs @@ -1,2 +1,3 @@ pub mod ad5680; pub mod max1968; +pub mod thermostat; \ No newline at end of file diff --git a/src/thermostat/thermostat.rs b/src/thermostat/thermostat.rs index 832ad9a..91f5d70 100644 --- a/src/thermostat/thermostat.rs +++ b/src/thermostat/thermostat.rs @@ -1,5 +1,14 @@ +use crate::sys_timer; use crate::thermostat::ad5680; -use crate::thermostat::MAX1968; +use crate::thermostat::max1968::{MAX1968, AdcReadTarget, DAC_OUT_V_MAX}; +use log::info; +use uom::si::{ + electric_current::ampere, + electric_potential::{millivolt, volt}, + electrical_resistance::ohm, + f64::{ElectricCurrent, ElectricPotential, ElectricalResistance}, + ratio::ratio, +}; pub struct Thermostat { max1968: MAX1968, @@ -15,4 +24,65 @@ impl Thermostat{ pub fn setup(&mut self){ self.max1968.setup(); } + + /// 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); + let before_cal = self.max1968.phy.center_pt; + 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; + } + } + + loop { + info!("Before Calibration, VREF = {:?}", before_cal); + info!("After Calibration, VREF = {:?}", self.max1968.phy.center_pt); + self.max1968.set_i(ElectricCurrent::new::(0.0)); + info!("VREF Value {:?}", self.max1968.adc_read(AdcReadTarget::VREF, 64)); + info!("DAC VFB Value {:?}", self.max1968.adc_read(AdcReadTarget::DacVfb, 64)); + sys_timer::sleep(100); + } + } + } \ No newline at end of file