use core::marker::PhantomData; use core::u16; use crate::thermostat::ad5680; use fugit::KilohertzU32; use stm32f4xx_hal::{ adc::{ config::{self, AdcConfig}, Adc, }, gpio::{gpioa::*, gpiob::*, gpioc::*, Alternate, Analog, Output, PushPull}, hal::{self, blocking::spi::Transfer, digital::v2::OutputPin}, 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: KilohertzU32 = KilohertzU32::from_raw(20); 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; 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 trait ChannelPins { type DacSpi: Transfer; type DacSync: OutputPin; type ShdnPin: OutputPin; type VRefPin; type ItecPin; type DacFeedbackPin; type VTecPin; type MaxVPin; type MaxIPosPin; type MAXINegPin; } pub struct Channel0; impl ChannelPins for Channel0 { type DacSpi = DacSpi; type DacSync = DacSync; type ShdnPin = PA5>; type VRefPin = PA6; type ItecPin = PB1; // Fixme: Flywire is added to Rev0_2 prototype. In Rev0_3, it is connected to PC0 //type DacFeedbackPin = PC0; type DacFeedbackPin = PC3; type VTecPin = PB0; type MaxVPin = PwmChannel; type MaxIPosPin = PwmChannel; type MAXINegPin = PwmChannel; } pub struct MAX1968Phy { // state pub center_pt: ElectricPotential, pub dac: ad5680::Dac, pub shdn: C::ShdnPin, pub vref_pin: C::VRefPin, pub itec_pin: C::ItecPin, pub dac_feedback_pin: C::DacFeedbackPin, pub vtec_pin: C::VTecPin, pub max_v: C::MaxVPin, pub max_i_pos: C::MaxIPosPin, pub max_i_neg: C::MAXINegPin, } pub struct MAX1968PinSet { pub dac: ad5680::Dac, pub shdn: C::ShdnPin, pub vref_pin: C::VRefPin, pub itec_pin: C::ItecPin, pub dac_feedback_pin: C::DacFeedbackPin, pub vtec_pin: C::VTecPin, pub max_v: C::MaxVPin, pub max_i_pos: C::MaxIPosPin, pub max_i_neg: C::MAXINegPin, } type DacSpi = Spi>, NoMiso, PB5>), TransferModeNormal>; type DacSync = PB4>; pub struct MaxAdcPins { pub dac_vfb: PC0, pub vref: PA6, pub itec: PB1, pub vtec: PB0, } pub struct MAX1968 { // settings pub phy: MAX1968Phy, pub pins_adc: Adc, } pub struct PwmPins { pub max_v0: PwmChannel, pub max_i_pos0: PwmChannel, pub max_i_neg0: PwmChannel, } 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 { fn init_pwm_pin>(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, PWM_FREQ_KHZ.convert(), &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 MAX1968Phy { pub fn new(pins: MAX1968PinSet) -> Self { MAX1968Phy { center_pt: ElectricPotential::new::(1.5), dac: pins.dac, shdn: pins.shdn, vref_pin: pins.vref_pin, itec_pin: pins.itec_pin, dac_feedback_pin: pins.dac_feedback_pin, vtec_pin: pins.vtec_pin, max_v: pins.max_v, max_i_pos: pins.max_i_pos, max_i_neg: pins.max_i_neg, } } } impl MAX1968 { pub fn new(phy_ch0: MAX1968Phy, adc1: ADC1) -> Self { 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 { phy: phy_ch0, pins_adc: pins_adc, } } pub fn setup(&mut self) { self.power_down(); let vref = self.adc_read(AdcReadTarget::VREF, 2048); self.set_center_point(vref); // Todo: Add Calibration here 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.set_i(ElectricCurrent::new::(0.0)); } pub fn power_down(&mut self) { let _ = self.phy.shdn.set_low(); } pub fn power_up(&mut self) { let _ = self.phy.shdn.set_high(); } pub fn set_center_point(&mut self, value: ElectricPotential) { self.phy.center_pt = value; } fn set_dac(&mut self, voltage: ElectricPotential) -> ElectricPotential { let value = ((voltage / ElectricPotential::new::(DAC_OUT_V_MAX)).get::() * (ad5680::MAX_VALUE as f64)) as u32; self.phy.dac.set(value).unwrap(); // TODO: Store the set-ed DAC Voltage Value voltage } pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent { let voltage = i_tec * 10.0 * R_SENSE + self.phy.center_pt; let voltage = self.set_dac(voltage); 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 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 => { for _ in (0..avg_pt).rev() { sample += self.pins_adc.convert( &self.phy.vref_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480, ) as u32; } sample / avg_pt as u32 } AdcReadTarget::DacVfb => { for _ in (0..avg_pt).rev() { sample += self.pins_adc.convert( &self.phy.dac_feedback_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480, ) as u32; } sample / avg_pt as u32 } AdcReadTarget::ITec => { for _ in (0..avg_pt).rev() { sample += self.pins_adc.convert( &self.phy.itec_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480, ) as u32; } sample / avg_pt as u32 } AdcReadTarget::VTec => { for _ in (0..avg_pt).rev() { sample += self.pins_adc.convert( &self.phy.vtec_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::(mv as f64) } pub fn get_vref(&mut self) -> ElectricPotential { self.adc_read(AdcReadTarget::VREF, 16) } pub fn get_dac_vfb(&mut self) -> ElectricPotential { self.adc_read(AdcReadTarget:: DacVfb, 16) } pub fn get_tec_i(&mut self) -> ElectricCurrent { (self.adc_read(AdcReadTarget::ITec, 1) - self.phy.center_pt) / ElectricalResistance::new::(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) - TEC_VSEC_BIAS_V) * 4.0 } fn set_pwm(&mut self, pwm_pin: PwmPinsEnum, duty: f64, max_duty: f64) -> f64 { fn set>(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.phy.max_v, duty), PwmPinsEnum::MaxPosI => set(&mut self.phy.max_i_pos, duty), PwmPinsEnum::MaxNegI => set(&mut self.phy.max_i_neg, duty), } } 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.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.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.set_pwm(PwmPinsEnum::MaxNegI, duty, MAX_I_NEG_DUTY_MAX); duty * MAX_I_POS_NEG_DUTY_TO_CURRENT_RATE } }