285 lines
9.2 KiB
Rust
285 lines
9.2 KiB
Rust
|
use core::u16;
|
||
|
|
||
|
use crate::thermostat::ad5680;
|
||
|
|
||
|
use fugit::RateExtU32;
|
||
|
use stm32f4xx_hal::{
|
||
|
adc::{
|
||
|
config::{self, AdcConfig},
|
||
|
Adc,
|
||
|
},
|
||
|
gpio::{gpioa::*, gpiob::*, gpioc::*, Alternate, Analog, Output, PushPull},
|
||
|
hal,
|
||
|
pac::{ADC1, SPI1, TIM4},
|
||
|
rcc::Clocks,
|
||
|
spi::{NoMiso, Spi, TransferModeNormal},
|
||
|
timer::pwm::{PwmChannel, PwmExt},
|
||
|
};
|
||
|
|
||
|
use uom::si::{
|
||
|
electric_current::ampere,
|
||
|
electric_potential::{millivolt, volt},
|
||
|
electrical_resistance::ohm,
|
||
|
f64::{ElectricCurrent, ElectricPotential, ElectricalResistance},
|
||
|
ratio::ratio,
|
||
|
};
|
||
|
|
||
|
pub const PWM_FREQ_KHZ: u32 = 20;
|
||
|
pub const R_SENSE: f64 = 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
|
||
|
const DAC_OUT_V_MAX: f64 = 3.3;
|
||
|
const TEC_VSEC_BIAS_V: f64 = 1.65;
|
||
|
const MAX_V_DUTY_MAX: f64 = 1.5 / 3.3;
|
||
|
const MAX_I_POS_DUTY_MAX: f64 = 1.5 / 3.3;
|
||
|
const MAX_I_NEG_DUTY_MAX: f64 = 1.5 / 3.3;
|
||
|
|
||
|
pub struct MAX1968PinSet {
|
||
|
pub dac_spi: DacSpi,
|
||
|
pub dac_sync: DacSync,
|
||
|
pub shdn: PA5<Output<PushPull>>,
|
||
|
pub dac_vfb: PC0<Analog>,
|
||
|
pub vref: PA6<Analog>,
|
||
|
pub vtec: PB0<Analog>,
|
||
|
pub itec: PB1<Analog>,
|
||
|
pub max_v0: PwmChannel<TIM4, 1>,
|
||
|
pub max_i_pos0: PwmChannel<TIM4, 2>,
|
||
|
pub max_i_neg0: PwmChannel<TIM4, 0>,
|
||
|
}
|
||
|
|
||
|
type DacSpi = Spi<SPI1, (PB3<Alternate<5>>, NoMiso, PB5<Alternate<5>>), TransferModeNormal>;
|
||
|
type DacSync = PB4<Output<PushPull>>;
|
||
|
pub struct MaxAdcPins {
|
||
|
pub dac_vfb: PC0<Analog>,
|
||
|
pub vref: PA6<Analog>,
|
||
|
pub itec: PB1<Analog>,
|
||
|
pub vtec: PB0<Analog>,
|
||
|
}
|
||
|
|
||
|
pub struct MAX1968 {
|
||
|
pub center_pt: ElectricPotential, // To be moved to a miniconf crate's struct
|
||
|
pub adc: Adc<ADC1>,
|
||
|
pub dac: ad5680::Dac<DacSpi, DacSync>,
|
||
|
pub shdn: PA5<Output<PushPull>>,
|
||
|
pub adc_pins: MaxAdcPins,
|
||
|
pub pwm_pins: PwmPins,
|
||
|
}
|
||
|
pub struct PwmPins {
|
||
|
pub max_v0: PwmChannel<TIM4, 1>,
|
||
|
pub max_i_pos0: PwmChannel<TIM4, 2>,
|
||
|
pub max_i_neg0: PwmChannel<TIM4, 0>,
|
||
|
}
|
||
|
enum PwmPinsEnum {
|
||
|
MaxV,
|
||
|
MaxPosI,
|
||
|
MaxNegI,
|
||
|
}
|
||
|
|
||
|
pub enum AdcReadTarget {
|
||
|
VREF,
|
||
|
DacVfb,
|
||
|
ITec,
|
||
|
VTec,
|
||
|
}
|
||
|
|
||
|
impl PwmPins {
|
||
|
fn setup(clocks: Clocks, tim4: TIM4, max_v0: PB7, max_i_pos0: PB8, max_i_neg0: PB6) -> PwmPins {
|
||
|
let freq = 20.kHz();
|
||
|
|
||
|
fn init_pwm_pin<P: hal::PwmPin<Duty = u16>>(pin: &mut P) {
|
||
|
pin.set_duty(0);
|
||
|
pin.enable();
|
||
|
}
|
||
|
|
||
|
let channels = (
|
||
|
max_i_neg0.into_alternate::<2>(),
|
||
|
max_v0.into_alternate::<2>(),
|
||
|
max_i_pos0.into_alternate::<2>(),
|
||
|
);
|
||
|
|
||
|
let (mut max_i_neg0, mut max_v0, mut max_i_pos0) =
|
||
|
tim4.pwm_hz(channels, freq, &clocks).split();
|
||
|
|
||
|
init_pwm_pin(&mut max_v0);
|
||
|
init_pwm_pin(&mut max_i_neg0);
|
||
|
init_pwm_pin(&mut max_i_pos0);
|
||
|
|
||
|
PwmPins {
|
||
|
max_v0,
|
||
|
max_i_pos0,
|
||
|
max_i_neg0,
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl MAX1968 {
|
||
|
pub fn new(pins: MAX1968PinSet, adc1: ADC1) -> Self {
|
||
|
let dac = ad5680::Dac::new(pins.dac_spi, pins.dac_sync);
|
||
|
|
||
|
let config = AdcConfig::default()
|
||
|
.clock(config::Clock::Pclk2_div_2)
|
||
|
.default_sample_time(config::SampleTime::Cycles_480);
|
||
|
|
||
|
let pins_adc = Adc::adc1(adc1, true, config);
|
||
|
|
||
|
MAX1968 {
|
||
|
center_pt: ElectricPotential::new::<millivolt>(1500.0),
|
||
|
adc: pins_adc,
|
||
|
dac: dac,
|
||
|
shdn: pins.shdn,
|
||
|
adc_pins: MaxAdcPins {
|
||
|
dac_vfb: pins.dac_vfb,
|
||
|
vref: pins.vref,
|
||
|
itec: pins.itec,
|
||
|
vtec: pins.vtec,
|
||
|
},
|
||
|
pwm_pins: PwmPins {
|
||
|
max_v0: pins.max_v0,
|
||
|
max_i_pos0: pins.max_i_pos0,
|
||
|
max_i_neg0: pins.max_i_neg0,
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn power_down(&mut self) {
|
||
|
let _ = self.shdn.set_high();
|
||
|
}
|
||
|
|
||
|
pub fn power_up(&mut self) {
|
||
|
let _ = self.shdn.set_high();
|
||
|
}
|
||
|
|
||
|
pub fn set_center_point(&mut self, value: ElectricPotential) {
|
||
|
self.center_pt = value;
|
||
|
}
|
||
|
|
||
|
fn set_dac(&mut self, voltage: ElectricPotential) -> ElectricPotential {
|
||
|
let value = ((voltage / ElectricPotential::new::<volt>(DAC_OUT_V_MAX)).get::<ratio>()
|
||
|
* (ad5680::MAX_VALUE as f64)) as u32;
|
||
|
self.dac.set(value).unwrap();
|
||
|
// TODO: Store the set-ed DAC Voltage Value
|
||
|
voltage
|
||
|
}
|
||
|
|
||
|
pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent {
|
||
|
let center_point = self.center_pt;
|
||
|
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
|
||
|
let voltage = i_tec * 10.0 * r_sense + center_point;
|
||
|
let voltage = self.set_dac(voltage);
|
||
|
let i_tec = (voltage - center_point) / (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 {
|
||
|
let mut sample: u32 = 0;
|
||
|
sample = match adc_read_target {
|
||
|
AdcReadTarget::VREF => {
|
||
|
for _ in (0..avg_pt).rev() {
|
||
|
sample += self.adc.convert(
|
||
|
&self.adc_pins.vref,
|
||
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480,
|
||
|
) as u32;
|
||
|
}
|
||
|
sample / avg_pt as u32
|
||
|
}
|
||
|
AdcReadTarget::DacVfb => {
|
||
|
for _ in (0..avg_pt).rev() {
|
||
|
sample += self.adc.convert(
|
||
|
&self.adc_pins.dac_vfb,
|
||
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480,
|
||
|
) as u32;
|
||
|
}
|
||
|
sample / avg_pt as u32
|
||
|
}
|
||
|
AdcReadTarget::ITec => {
|
||
|
for _ in (0..avg_pt).rev() {
|
||
|
sample += self.adc.convert(
|
||
|
&self.adc_pins.itec,
|
||
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480,
|
||
|
) as u32;
|
||
|
}
|
||
|
sample / avg_pt as u32
|
||
|
}
|
||
|
AdcReadTarget::VTec => {
|
||
|
for _ in (0..avg_pt).rev() {
|
||
|
sample += self.adc.convert(
|
||
|
&self.adc_pins.vtec,
|
||
|
stm32f4xx_hal::adc::config::SampleTime::Cycles_480,
|
||
|
) as u32;
|
||
|
}
|
||
|
sample / avg_pt as u32
|
||
|
}
|
||
|
};
|
||
|
let mv = self.adc.sample_to_millivolts(sample as u16);
|
||
|
ElectricPotential::new::<millivolt>(mv as f64)
|
||
|
}
|
||
|
|
||
|
pub fn get_vref(&mut self) -> ElectricPotential {
|
||
|
self.adc_read(AdcReadTarget::VREF, 16)
|
||
|
}
|
||
|
|
||
|
pub fn get_dac_vfb(&mut self) -> ElectricPotential {
|
||
|
// Fixme: Rev0_2 does not have this feature
|
||
|
unimplemented!()
|
||
|
//self.adc_read(AdcReadTarget:: DacVfb, 1)
|
||
|
}
|
||
|
|
||
|
pub fn get_tec_i(&mut self) -> ElectricCurrent {
|
||
|
(self.adc_read(AdcReadTarget::ITec, 1) - self.center_pt)
|
||
|
/ ElectricalResistance::new::<ohm>(0.4)
|
||
|
}
|
||
|
|
||
|
pub fn get_tec_v(&mut self) -> ElectricPotential {
|
||
|
// Fixme: Rev0_2 has Analog Input Polarity Reversed
|
||
|
// Remove the -ve sign for Rev0_3
|
||
|
-(self.adc_read(AdcReadTarget::VTec, 1) - ElectricPotential::new::<volt>(TEC_VSEC_BIAS_V))
|
||
|
* 4.0
|
||
|
}
|
||
|
|
||
|
fn set_pwm(&mut self, pwm_pin: PwmPinsEnum, duty: f64, max_duty: f64) -> f64 {
|
||
|
fn set<P: hal::PwmPin<Duty = u16>>(pin: &mut P, duty: f64) -> f64 {
|
||
|
let max = pin.get_max_duty();
|
||
|
let value = ((duty * (max as f64)) as u16).min(max);
|
||
|
pin.set_duty(value);
|
||
|
pin.enable();
|
||
|
value as f64 / (max as f64)
|
||
|
}
|
||
|
|
||
|
let duty = duty.min(max_duty);
|
||
|
|
||
|
match pwm_pin {
|
||
|
PwmPinsEnum::MaxV => set(&mut self.pwm_pins.max_v0, duty),
|
||
|
PwmPinsEnum::MaxPosI => set(&mut self.pwm_pins.max_i_pos0, duty),
|
||
|
PwmPinsEnum::MaxNegI => set(&mut self.pwm_pins.max_i_neg0, duty),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn set_max_v(&mut self, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) {
|
||
|
let max = ElectricPotential::new::<volt>(6.0);
|
||
|
let v = max_v / 4.0;
|
||
|
let duty = (v / ElectricPotential::new::<volt>(3.3)).get::<ratio>();
|
||
|
let duty = self.set_pwm(PwmPinsEnum::MaxV, duty, MAX_V_DUTY_MAX);
|
||
|
(duty * max, max)
|
||
|
}
|
||
|
|
||
|
pub fn set_max_i_pos(&mut self, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
|
||
|
let max = ElectricCurrent::new::<ampere>(3.0);
|
||
|
let v = 10.0 * max_i_pos * ElectricalResistance::new::<ohm>(R_SENSE);
|
||
|
let duty = (v / ElectricPotential::new::<volt>(3.3)).get::<ratio>();
|
||
|
let duty = self.set_pwm(PwmPinsEnum::MaxPosI, duty, MAX_I_POS_DUTY_MAX);
|
||
|
(duty * max * 3.3 / 1.5, max)
|
||
|
}
|
||
|
|
||
|
pub fn set_max_i_neg(&mut self, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
|
||
|
let max = ElectricCurrent::new::<ampere>(3.0);
|
||
|
let v = 10.0 * max_i_neg * ElectricalResistance::new::<ohm>(R_SENSE);
|
||
|
let duty = (v / ElectricPotential::new::<volt>(3.3)).get::<ratio>();
|
||
|
let duty = self.set_pwm(PwmPinsEnum::MaxNegI, duty, MAX_I_NEG_DUTY_MAX);
|
||
|
(duty * max * 3.3 / 1.5, max)
|
||
|
}
|
||
|
}
|