Compare commits

...

2 Commits

Author SHA1 Message Date
linuswck 1fcfe41a63 Add averaging filter on the pin_adc readings
- Adapted from Kirdy Firmware
- Can reduce the i_tec readings noise dispersion
2024-05-02 16:49:55 +08:00
linuswck 9fce19a418 Revert "Disable feedback current readout on flawed HW Revs"
This reverts commit ae3d8b51d4.
2024-05-02 14:38:40 +08:00
3 changed files with 114 additions and 108 deletions

View File

@ -271,8 +271,6 @@ with the following keys.
| `tec_u_meas` | Volts | Measurement of the voltage across the TEC | | `tec_u_meas` | Volts | Measurement of the voltage across the TEC |
| `pid_output` | Amperes | PID control output | | `pid_output` | Amperes | PID control output |
Note: With Thermostat v2 and below, the voltage and current readouts `i_tec` and `tec_i` are disabled and null due to faulty hardware that introduces a lot of noise in the signal.
## PID Tuning ## PID Tuning
The thermostat implements a PID control loop for each of the TEC channels, more details on setting up the PID control loop can be found [here](./doc/PID%20tuning.md). The thermostat implements a PID control loop for each of the TEC channels, more details on setting up the PID control loop can be found [here](./doc/PID%20tuning.md).

View File

@ -20,9 +20,15 @@ use crate::{
command_handler::JsonBuffer, command_handler::JsonBuffer,
pins::{self, Channel0VRef, Channel1VRef}, pins::{self, Channel0VRef, Channel1VRef},
steinhart_hart, steinhart_hart,
hw_rev,
}; };
pub enum PinsAdcReadTarget {
VREF,
DacVfb,
ITec,
VTec,
}
pub const CHANNELS: usize = 2; pub const CHANNELS: usize = 2;
pub const R_SENSE: f64 = 0.05; pub const R_SENSE: f64 = 0.05;
@ -33,18 +39,17 @@ pub const MAX_TEC_I: f64 = 3.0;
const DAC_OUT_V_MAX: f64 = 3.0; const DAC_OUT_V_MAX: f64 = 3.0;
// TODO: -pub // TODO: -pub
pub struct Channels<'a> { pub struct Channels {
channel0: Channel<Channel0>, channel0: Channel<Channel0>,
channel1: Channel<Channel1>, channel1: Channel<Channel1>,
pub adc: ad7172::Adc<pins::AdcSpi, pins::AdcNss>, pub adc: ad7172::Adc<pins::AdcSpi, pins::AdcNss>,
/// stm32f4 integrated adc /// stm32f4 integrated adc
pins_adc: pins::PinsAdc, pins_adc: pins::PinsAdc,
pub pwm: pins::PwmPins, pub pwm: pins::PwmPins,
hwrev: &'a hw_rev::HWRev,
} }
impl<'a> Channels<'a> { impl Channels {
pub fn new(pins: pins::Pins, hwrev: &'a hw_rev::HWRev) -> Self { pub fn new(pins: pins::Pins) -> Self {
let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap(); let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap();
// Feature not used // Feature not used
adc.set_sync_enable(false).unwrap(); adc.set_sync_enable(false).unwrap();
@ -62,7 +67,7 @@ impl<'a> Channels<'a> {
let channel1 = Channel::new(pins.channel1, adc_calibration1); let channel1 = Channel::new(pins.channel1, adc_calibration1);
let pins_adc = pins.pins_adc; let pins_adc = pins.pins_adc;
let pwm = pins.pwm; let pwm = pins.pwm;
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm, hwrev }; let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm };
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
channels.calibrate_dac_value(channel); channels.calibrate_dac_value(channel);
channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0)); channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0));
@ -104,7 +109,7 @@ impl<'a> Channels<'a> {
pub fn get_center(&mut self, channel: usize) -> ElectricPotential { pub fn get_center(&mut self, channel: usize) -> ElectricPotential {
match self.channel_state(channel).center { match self.channel_state(channel).center {
CenterPoint::Vref => CenterPoint::Vref =>
self.read_vref(channel), self.adc_read(channel, PinsAdcReadTarget::VREF, 8),
CenterPoint::Override(center_point) => CenterPoint::Override(center_point) =>
ElectricPotential::new::<volt>(center_point.into()), ElectricPotential::new::<volt>(center_point.into()),
} }
@ -153,32 +158,112 @@ impl<'a> Channels<'a> {
i_set i_set
} }
pub fn read_dac_feedback(&mut self, channel: usize) -> ElectricPotential { /// AN4073: ADC Reading Dispersion can be reduced through Averaging
pub fn adc_read(&mut self, channel: usize, adc_read_target: PinsAdcReadTarget, avg_pt: u16) -> ElectricPotential {
let mut sample: u32 = 0;
match channel { match channel {
0 => { 0 => {
let sample = self.pins_adc.convert( sample = match adc_read_target {
&self.channel0.dac_feedback_pin, PinsAdcReadTarget::VREF => {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 match &self.channel0.vref_pin {
); Channel0VRef::Analog(vref_pin) => {
let mv = self.pins_adc.sample_to_millivolts(sample); for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(vref_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
},
Channel0VRef::Disabled(_) => {2048 as u32}
}
}
PinsAdcReadTarget::DacVfb => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel0.dac_feedback_pin,stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::ITec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel0.itec_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::VTec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel0.tec_u_meas_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
};
let mv = self.pins_adc.sample_to_millivolts(sample as u16);
ElectricPotential::new::<millivolt>(mv as f64) ElectricPotential::new::<millivolt>(mv as f64)
} }
1 => { 1 => {
let sample = self.pins_adc.convert( sample = match adc_read_target {
&self.channel1.dac_feedback_pin, PinsAdcReadTarget::VREF => {
stm32f4xx_hal::adc::config::SampleTime::Cycles_480 match &self.channel1.vref_pin {
); Channel1VRef::Analog(vref_pin) => {
let mv = self.pins_adc.sample_to_millivolts(sample); for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(vref_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
},
Channel1VRef::Disabled(_) => {2048 as u32}
}
}
PinsAdcReadTarget::DacVfb => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel1.dac_feedback_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::ITec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel1.itec_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::VTec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel1.tec_u_meas_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
};
let mv = self.pins_adc.sample_to_millivolts(sample as u16);
ElectricPotential::new::<millivolt>(mv as f64) ElectricPotential::new::<millivolt>(mv as f64)
} }
_ => unreachable!(), _ => unreachable!()
} }
} }
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential { pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential {
let mut prev = self.read_dac_feedback(channel); let mut prev = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
loop { loop {
let current = self.read_dac_feedback(channel); let current = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
if (current - prev).abs() < tolerance { if (current - prev).abs() < tolerance {
return current; return current;
} }
@ -186,83 +271,6 @@ impl<'a> Channels<'a> {
} }
} }
pub fn read_itec(&mut self, channel: usize) -> ElectricPotential {
match channel {
0 => {
let sample = self.pins_adc.convert(
&self.channel0.itec_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
1 => {
let sample = self.pins_adc.convert(
&self.channel1.itec_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
_ => unreachable!(),
}
}
/// should be 1.5V
pub fn read_vref(&mut self, channel: usize) -> ElectricPotential {
match channel {
0 => {
match &self.channel0.vref_pin {
Channel0VRef::Analog(vref_pin) => {
let sample = self.pins_adc.convert(
vref_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
},
Channel0VRef::Disabled(_) => ElectricPotential::new::<volt>(1.5)
}
}
1 => {
match &self.channel1.vref_pin {
Channel1VRef::Analog(vref_pin) => {
let sample = self.pins_adc.convert(
vref_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
},
Channel1VRef::Disabled(_) => ElectricPotential::new::<volt>(1.5)
}
}
_ => unreachable!(),
}
}
pub fn read_tec_u_meas(&mut self, channel: usize) -> ElectricPotential {
match channel {
0 => {
let sample = self.pins_adc.convert(
&self.channel0.tec_u_meas_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
1 => {
let sample = self.pins_adc.convert(
&self.channel1.tec_u_meas_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
_ => unreachable!(),
}
}
/// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output. /// 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 thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
@ -390,12 +398,12 @@ impl<'a> Channels<'a> {
// Get current passing through TEC // Get current passing through TEC
pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent { pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent {
(self.read_itec(channel) - self.read_vref(channel)) / ElectricalResistance::new::<ohm>(0.4) (self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::<ohm>(0.4)
} }
// Get voltage across TEC // Get voltage across TEC
pub fn get_tec_v(&mut self, channel: usize) -> ElectricPotential { pub fn get_tec_v(&mut self, channel: usize) -> ElectricPotential {
(self.read_tec_u_meas(channel) - ElectricPotential::new::<volt>(1.5)) * 4.0 (self.adc_read(channel, PinsAdcReadTarget::VTec, 16) - ElectricPotential::new::<volt>(1.5)) * 4.0
} }
fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 { fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 {
@ -448,8 +456,8 @@ impl<'a> Channels<'a> {
fn report(&mut self, channel: usize) -> Report { fn report(&mut self, channel: usize) -> Report {
let i_set = self.get_i(channel); let i_set = self.get_i(channel);
let i_tec = if self.hwrev.major > 2 {Some(self.read_itec(channel))} else {None}; let i_tec = self.adc_read(channel, PinsAdcReadTarget::ITec, 16);
let tec_i = if self.hwrev.major > 2 {Some(self.get_tec_i(channel))} else {None}; let tec_i = self.get_tec_i(channel);
let dac_value = self.get_dac(channel); let dac_value = self.get_dac(channel);
let state = self.channel_state(channel); let state = self.channel_state(channel);
let pid_output = ElectricCurrent::new::<ampere>(state.pid.y1); let pid_output = ElectricCurrent::new::<ampere>(state.pid.y1);
@ -464,7 +472,7 @@ impl<'a> Channels<'a> {
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set, i_set,
dac_value, dac_value,
dac_feedback: self.read_dac_feedback(channel), dac_feedback: self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1),
i_tec, i_tec,
tec_i, tec_i,
tec_u_meas: self.get_tec_v(channel), tec_u_meas: self.get_tec_v(channel),
@ -562,8 +570,8 @@ pub struct Report {
i_set: ElectricCurrent, i_set: ElectricCurrent,
dac_value: ElectricPotential, dac_value: ElectricPotential,
dac_feedback: ElectricPotential, dac_feedback: ElectricPotential,
i_tec: Option<ElectricPotential>, i_tec: ElectricPotential,
tec_i: Option<ElectricCurrent>, tec_i: ElectricCurrent,
tec_u_meas: ElectricPotential, tec_u_meas: ElectricPotential,
pid_output: ElectricCurrent, pid_output: ElectricCurrent,
} }

View File

@ -138,7 +138,7 @@ fn main() -> ! {
let mut store = flash_store::store(dp.FLASH); let mut store = flash_store::store(dp.FLASH);
let mut channels = Channels::new(pins, &hwrev); let mut channels = Channels::new(pins);
for c in 0..CHANNELS { for c in 0..CHANNELS {
match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) { match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) {
Ok(Some(config)) => Ok(Some(config)) =>