forked from M-Labs/thermostat
Compare commits
No commits in common. "1fcfe41a63f1d6996daceb3945e24ee9d68b7909" and "00d5feaa8d8c46617fd6c7c38f5f469af5aa67bd" have entirely different histories.
1fcfe41a63
...
00d5feaa8d
|
@ -271,6 +271,8 @@ with the following keys.
|
|||
| `tec_u_meas` | Volts | Measurement of the voltage across the TEC |
|
||||
| `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
|
||||
|
||||
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).
|
||||
|
|
218
src/channels.rs
218
src/channels.rs
|
@ -20,15 +20,9 @@ use crate::{
|
|||
command_handler::JsonBuffer,
|
||||
pins::{self, Channel0VRef, Channel1VRef},
|
||||
steinhart_hart,
|
||||
hw_rev,
|
||||
};
|
||||
|
||||
pub enum PinsAdcReadTarget {
|
||||
VREF,
|
||||
DacVfb,
|
||||
ITec,
|
||||
VTec,
|
||||
}
|
||||
|
||||
pub const CHANNELS: usize = 2;
|
||||
pub const R_SENSE: f64 = 0.05;
|
||||
|
||||
|
@ -39,17 +33,18 @@ pub const MAX_TEC_I: f64 = 3.0;
|
|||
const DAC_OUT_V_MAX: f64 = 3.0;
|
||||
|
||||
// TODO: -pub
|
||||
pub struct Channels {
|
||||
pub struct Channels<'a> {
|
||||
channel0: Channel<Channel0>,
|
||||
channel1: Channel<Channel1>,
|
||||
pub adc: ad7172::Adc<pins::AdcSpi, pins::AdcNss>,
|
||||
/// stm32f4 integrated adc
|
||||
pins_adc: pins::PinsAdc,
|
||||
pub pwm: pins::PwmPins,
|
||||
hwrev: &'a hw_rev::HWRev,
|
||||
}
|
||||
|
||||
impl Channels {
|
||||
pub fn new(pins: pins::Pins) -> Self {
|
||||
impl<'a> Channels<'a> {
|
||||
pub fn new(pins: pins::Pins, hwrev: &'a hw_rev::HWRev) -> Self {
|
||||
let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap();
|
||||
// Feature not used
|
||||
adc.set_sync_enable(false).unwrap();
|
||||
|
@ -67,7 +62,7 @@ impl Channels {
|
|||
let channel1 = Channel::new(pins.channel1, adc_calibration1);
|
||||
let pins_adc = pins.pins_adc;
|
||||
let pwm = pins.pwm;
|
||||
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm };
|
||||
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm, hwrev };
|
||||
for channel in 0..CHANNELS {
|
||||
channels.calibrate_dac_value(channel);
|
||||
channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0));
|
||||
|
@ -109,7 +104,7 @@ impl Channels {
|
|||
pub fn get_center(&mut self, channel: usize) -> ElectricPotential {
|
||||
match self.channel_state(channel).center {
|
||||
CenterPoint::Vref =>
|
||||
self.adc_read(channel, PinsAdcReadTarget::VREF, 8),
|
||||
self.read_vref(channel),
|
||||
CenterPoint::Override(center_point) =>
|
||||
ElectricPotential::new::<volt>(center_point.into()),
|
||||
}
|
||||
|
@ -158,112 +153,32 @@ impl Channels {
|
|||
i_set
|
||||
}
|
||||
|
||||
/// 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;
|
||||
pub fn read_dac_feedback(&mut self, channel: usize) -> ElectricPotential {
|
||||
match channel {
|
||||
0 => {
|
||||
sample = match adc_read_target {
|
||||
PinsAdcReadTarget::VREF => {
|
||||
match &self.channel0.vref_pin {
|
||||
Channel0VRef::Analog(vref_pin) => {
|
||||
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);
|
||||
let sample = self.pins_adc.convert(
|
||||
&self.channel0.dac_feedback_pin,
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
1 => {
|
||||
sample = match adc_read_target {
|
||||
PinsAdcReadTarget::VREF => {
|
||||
match &self.channel1.vref_pin {
|
||||
Channel1VRef::Analog(vref_pin) => {
|
||||
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);
|
||||
let sample = self.pins_adc.convert(
|
||||
&self.channel1.dac_feedback_pin,
|
||||
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
|
||||
);
|
||||
let mv = self.pins_adc.sample_to_millivolts(sample);
|
||||
ElectricPotential::new::<millivolt>(mv as f64)
|
||||
}
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential {
|
||||
let mut prev = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
|
||||
let mut prev = self.read_dac_feedback(channel);
|
||||
loop {
|
||||
let current = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1);
|
||||
let current = self.read_dac_feedback(channel);
|
||||
if (current - prev).abs() < tolerance {
|
||||
return current;
|
||||
}
|
||||
|
@ -271,6 +186,83 @@ impl Channels {
|
|||
}
|
||||
}
|
||||
|
||||
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.
|
||||
///
|
||||
/// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
|
||||
|
@ -398,12 +390,12 @@ impl Channels {
|
|||
|
||||
// Get current passing through TEC
|
||||
pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent {
|
||||
(self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::<ohm>(0.4)
|
||||
(self.read_itec(channel) - self.read_vref(channel)) / ElectricalResistance::new::<ohm>(0.4)
|
||||
}
|
||||
|
||||
// Get voltage across TEC
|
||||
pub fn get_tec_v(&mut self, channel: usize) -> ElectricPotential {
|
||||
(self.adc_read(channel, PinsAdcReadTarget::VTec, 16) - ElectricPotential::new::<volt>(1.5)) * 4.0
|
||||
(self.read_tec_u_meas(channel) - ElectricPotential::new::<volt>(1.5)) * 4.0
|
||||
}
|
||||
|
||||
fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 {
|
||||
|
@ -456,8 +448,8 @@ impl Channels {
|
|||
|
||||
fn report(&mut self, channel: usize) -> Report {
|
||||
let i_set = self.get_i(channel);
|
||||
let i_tec = self.adc_read(channel, PinsAdcReadTarget::ITec, 16);
|
||||
let tec_i = self.get_tec_i(channel);
|
||||
let i_tec = if self.hwrev.major > 2 {Some(self.read_itec(channel))} else {None};
|
||||
let tec_i = if self.hwrev.major > 2 {Some(self.get_tec_i(channel))} else {None};
|
||||
let dac_value = self.get_dac(channel);
|
||||
let state = self.channel_state(channel);
|
||||
let pid_output = ElectricCurrent::new::<ampere>(state.pid.y1);
|
||||
|
@ -472,7 +464,7 @@ impl Channels {
|
|||
pid_engaged: state.pid_engaged,
|
||||
i_set,
|
||||
dac_value,
|
||||
dac_feedback: self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1),
|
||||
dac_feedback: self.read_dac_feedback(channel),
|
||||
i_tec,
|
||||
tec_i,
|
||||
tec_u_meas: self.get_tec_v(channel),
|
||||
|
@ -570,8 +562,8 @@ pub struct Report {
|
|||
i_set: ElectricCurrent,
|
||||
dac_value: ElectricPotential,
|
||||
dac_feedback: ElectricPotential,
|
||||
i_tec: ElectricPotential,
|
||||
tec_i: ElectricCurrent,
|
||||
i_tec: Option<ElectricPotential>,
|
||||
tec_i: Option<ElectricCurrent>,
|
||||
tec_u_meas: ElectricPotential,
|
||||
pid_output: ElectricCurrent,
|
||||
}
|
||||
|
|
|
@ -138,7 +138,7 @@ fn main() -> ! {
|
|||
|
||||
let mut store = flash_store::store(dp.FLASH);
|
||||
|
||||
let mut channels = Channels::new(pins);
|
||||
let mut channels = Channels::new(pins, &hwrev);
|
||||
for c in 0..CHANNELS {
|
||||
match store.read_value::<ChannelConfig>(CHANNEL_CONFIG_KEY[c]) {
|
||||
Ok(Some(config)) =>
|
||||
|
|
Loading…
Reference in New Issue