diff --git a/src/device/boot.rs b/src/device/boot.rs index 73aed98..f9090c8 100644 --- a/src/device/boot.rs +++ b/src/device/boot.rs @@ -13,6 +13,7 @@ use stm32f4xx_hal::{ watchdog::IndependentWatchdog, }; +use uom::si::electric_current::milliampere; use uom::si::{electric_current::ampere, f64::ElectricCurrent}; #[cfg(not(feature = "semihosting"))] @@ -65,6 +66,7 @@ pub fn bootup( laser.ld_open(); laser.set_ld_drive_current_limit(ElectricCurrent::new::(0.2)); laser.ld_set_i(ElectricCurrent::new::(0.15)); + laser.set_pd_i_limit(ElectricCurrent::new::(2.5)); laser.power_up(); let tec_driver = MAX1968::new(max1968_phy, perif.ADC1); @@ -76,6 +78,8 @@ pub fn bootup( thermostat.calibrate_dac_value(); thermostat.set_i(ElectricCurrent::new::(1.0)); + laser.set_pd_mon_calibrated_vdda(thermostat.get_calibrated_vdda()); + let flash_store = flash_store::store(perif.FLASH); let mut wd = IndependentWatchdog::new(perif.IWDG); diff --git a/src/laser_diode/analog_wdg.rs b/src/laser_diode/analog_wdg.rs index 95c3c5d..f76aa71 100644 --- a/src/laser_diode/analog_wdg.rs +++ b/src/laser_diode/analog_wdg.rs @@ -1,90 +1,133 @@ -// stm32f4xx_hal does not provide config and driver for analog watchdog yet +use stm32f4xx_hal::pac; +use stm32f4xx_hal::rcc::Enable; use stm32f4xx_hal::{ pac::{ADC2, NVIC}, gpio::{Analog, Output, PushPull, gpioa::PA3, gpiod::PD9}, interrupt, }; -use uom::si::electric_potential::millivolt; -use uom::si::f64::ElectricPotential; +use uom::si::{ + electric_potential::millivolt, + f64::ElectricPotential, + ratio::ratio +}; +use crate::info; // 12 bit Resolution const MAX_SAMPLE: u16 = 4095; - pub type LdPwrEnPinType = PD9>; pub type PdMonAdcPinType = PA3; +const PD_MON_ADC_CH_ID: u8 = 0x03; static mut ANALOG_WDG: Option = None; pub struct LdAnalogWdgPhy { - // To make sure PA3 is configured to Analog mode + // To make sure Pd Mon Pin is configured to Analog mode pub _pd_mon_ch0: PdMonAdcPinType, pub pwr_en_ch0: LdPwrEnPinType, } - #[derive(Clone)] -pub struct AlarmStatus { - alarm: bool, - val: u16, +pub struct Status { + pub pwr_excursion: bool, + pub v_tripped: ElectricPotential, + pub pwr_engaged: bool, + pub v: ElectricPotential, +} + +impl Default for Status { + fn default() -> Self { + Status { + pwr_excursion: false, + v_tripped: ElectricPotential::new::(0.0), + pwr_engaged: false, + v: ElectricPotential::new::(0.0), + } + } } pub struct LdAnalogWdg { pac: ADC2, phy: LdAnalogWdgPhy, - alarm_status: AlarmStatus, + alarm_status: Status, //Calibrated VDDA in millivolt from Adc.calibrate() calibrated_vdda: u32, } impl LdAnalogWdg { - /// ADC interrupt is disabled and continuous conversion is started by default - pub fn setup(pac: ADC2, phy: LdAnalogWdgPhy){ - unsafe { ANALOG_WDG = Some(LdAnalogWdg{ - pac: pac, - phy: phy, - alarm_status: AlarmStatus { - alarm: false, - val: 0x0000, - }, - calibrated_vdda: 3300, - }); + /// ADC Analog Watchdog is configured to guard a single regular Adc channel on Pd Mon Pin. + /// ADC is configured to start continuous conversion without using DMA immediately. + /// Interrupt is disabled by default. + pub fn setup(pac_adc: ADC2, mut phy: LdAnalogWdgPhy){ + unsafe { + // All ADCs share the same reset interface. + // NOTE(unsafe) this reference will only be used for atomic writes with no side effects. + let rcc = &(*pac::RCC::ptr()); + // Enable the ADC2 Clock + pac::ADC2::enable(rcc); + // Enable ADC Interrupt + NVIC::unmask(interrupt::ADC); } + pac_adc.cr1.reset(); + pac_adc.cr2.reset(); + pac_adc.sqr1.reset(); + pac_adc.sqr2.reset(); + pac_adc.sqr3.reset(); + + pac_adc.cr1.write(|w| w + // 12 Bit Resolution + .res().twelve_bit() + // Set Analog Watchdog to guard Single Regular Channel + .awden().enabled() + .awdsgl().single_channel() + .jawden().disabled() + // Disable Analog Watchdog Interrupt + .awdie().disabled() + // Set Analog Watchdog to monitor Pd Mon Pin + .awdch().variant(PD_MON_ADC_CH_ID) + ); + pac_adc.cr2.write(|w| w + // Continous Conversion Mode + .cont().set_bit() + // Power up ADC + .adon().set_bit() + // Set data alignment to the right + .align().right() + // End of conversion selection: Each Sequence + .eocs().each_sequence() + .exten().disabled() + .extsel().tim1cc1() + ); + // Set the Conversion Sequence to include Pd Mon Pin + pac_adc.sqr3.write(|w| w + .sq1().variant(PD_MON_ADC_CH_ID) + ); + // Set all sampling channel to have fastest sampling interval + pac_adc.smpr1.reset(); + pac_adc.smpr2.reset(); + + // Set the high threshold to be max value initially + pac_adc.htr.write(|w| w.ht().variant(MAX_SAMPLE)); + // Set the low threshold to be min value initially + pac_adc.ltr.write(|w| w.lt().variant(0)); + + // SWStart should only be set when ADON = 1. Otherwise no conversion is launched. + pac_adc.cr2.modify(|_, w| w + .swstart().set_bit() + ); + + // Turn LD Power Off by default + phy.pwr_en_ch0.set_low(); + unsafe { - if let Some(ref mut wdg ) = LdAnalogWdg::get() { - wdg.pac.cr1.write(|w| w - // 12 Bit Resolution - .res().twelve_bit() - // Enable Analog Watchdog on Single Regular Channel - .awden().enabled() - .awdsgl().single_channel() - .jawden().disabled() - // Disable Analog Watchdog Interrupt - .awdie().disabled() - // Select Analog Watchdog Channel 3 (PA3) on ADC2 - .awdch().bits(0x03) - ); - wdg.pac.cr2.write(|w| w - // Continous Conversion mode - .cont().set_bit() - // Enable ADC - .adon().set_bit() - // Start ADC Conversion - .swstart().set_bit() - ); - // Set Sampling Time for Channel 3 to 480 Cycle - wdg.pac.smpr2.write(|w| w - .smp3().cycles480() - ); - // Set the high threshold to be max value initially - wdg.pac.htr.write(|w| w.bits(0xFFFF_FFFF)); - // Set the high threshold to be min value initially - wdg.pac.ltr.write(|w| w.bits(0x0000_0000)); - // Set the Conversion Sequence to only have Channel 3 (PA3) - wdg.pac.sqr3.write(|w| w - .sq1().bits(0x03) - ); - } + ANALOG_WDG = Some( + LdAnalogWdg { + pac: pac_adc, + phy: phy, + alarm_status: Status::default(), + calibrated_vdda: 3300, + } + ); } } @@ -92,68 +135,53 @@ impl LdAnalogWdg { unsafe { ANALOG_WDG.as_mut() } } - /// This fn accepts the calibrated vdda value from Adc.calibrate() + fn convert_sample_to_volt(sample :u16) -> ElectricPotential { + if let Some(ref mut wdg ) = LdAnalogWdg::get() { + return ElectricPotential::new::(((u32::from(sample) * wdg.calibrated_vdda) / u32::from(MAX_SAMPLE)) as f64) + } + ElectricPotential::new::(0.0) + } + + pub fn set_trigger_threshold_v(htr: ElectricPotential){ + if let Some(ref mut wdg ) = LdAnalogWdg::get() { + let code: u32 = ((htr / (ElectricPotential::new::(wdg.calibrated_vdda as f64))).get::() * (MAX_SAMPLE as f64)) as u32; + wdg.pac.htr.write(|w| unsafe {w.bits(code)}); + info!("trigger_threshold_v: {:?}", code); + } + } + pub fn set_calibrated_vdda(val: u32) { if let Some(ref mut wdg ) = LdAnalogWdg::get() { wdg.calibrated_vdda = val; } } - pub fn get_pd_v() -> ElectricPotential { - if let Some(ref mut wdg ) = LdAnalogWdg::get() { - let sample = wdg.pac.dr.read().data().bits(); - - return ElectricPotential::new::(((u32::from(sample) * wdg.calibrated_vdda) / u32::from(MAX_SAMPLE)) as f64) - } - ElectricPotential::new::(0.0) - } - - fn set_alarm(){ - if let Some(ref mut wdg ) = LdAnalogWdg::get() { - wdg.alarm_status = AlarmStatus { - alarm: true, - val: wdg.pac.dr.read().data().bits(), - }; - } - } - - pub fn get_alarm_status() -> AlarmStatus { + pub fn get_status() -> Status { if let Some(ref mut wdg ) = LdAnalogWdg::get() { + wdg.alarm_status.v = LdAnalogWdg::convert_sample_to_volt(wdg.pac.dr.read().data().bits()); return wdg.alarm_status.clone() } - AlarmStatus { - alarm: false, - val: 0x0000, + Status::default() + } + + pub fn pwr_on_and_arm_protection(){ + if let Some(ref mut wdg ) = LdAnalogWdg::get() { + wdg.alarm_status = Status::default(); + LdAnalogWdg::pwr_on(); + // Interrupt should be enabled after power on to tackle the following edge case: + // Pd_Mon pin voltage has already exceed threshold before LD Power is on. + LdAnalogWdg::enable_watchdog_interrupt(); } } pub fn clear_alarm_status(){ if let Some(ref mut wdg ) = LdAnalogWdg::get() { - wdg.alarm_status = AlarmStatus { - alarm: false, - val: 0x0000, - }; + wdg.alarm_status.pwr_excursion = false; + wdg.alarm_status.v_tripped = ElectricPotential::new::(0.0); } } - /// Set ADC Watchdog Higher threshold register - /// Interrupt is triggered when ADC value is ABOVE the value set - pub fn set_htr(htr: u32){ - if let Some(ref mut wdg ) = LdAnalogWdg::get() { - wdg.pac.htr.write(|w| unsafe {w.bits(htr)}); - } - } - - /// Set ADC Watchdog Lower threshold register - /// Interrupt is triggered when ADC value is BELOW the value set - #[allow(unused)] - pub fn set_ltr(ltr:u32){ - if let Some(ref mut wdg ) = LdAnalogWdg::get() { - wdg.pac.ltr.write(|w| unsafe {w.bits(ltr)}); - } - } - - pub fn enable_watchdog_interrupt(){ + fn enable_watchdog_interrupt(){ if let Some(ref mut wdg ) = LdAnalogWdg::get() { wdg.pac.cr1.modify(|_, w| w .awdie().set_bit() @@ -161,7 +189,7 @@ impl LdAnalogWdg { } } - pub fn disable_watchdog_interrupt(){ + fn disable_watchdog_interrupt(){ if let Some(ref mut wdg ) = LdAnalogWdg::get() { wdg.pac.cr1.modify(|_, w| w .awdie().clear_bit() @@ -170,36 +198,41 @@ impl LdAnalogWdg { } fn clear_interrupt_bit(){ - unsafe{ - NVIC::unmask(interrupt::ADC); + if let Some(ref mut wdg ) = LdAnalogWdg::get() { + wdg.pac.sr.modify(|_, w| w + .awd().clear_bit() + ); } } - pub fn is_pwr_engaged() -> bool{ - if let Some(ref mut wdg ) = LdAnalogWdg::get() { - return wdg.phy.pwr_en_ch0.is_set_high() - } - false - } - - pub fn pwr_engage(){ + fn pwr_on(){ if let Some(ref mut wdg ) = LdAnalogWdg::get() { + wdg.alarm_status.pwr_engaged = true; wdg.phy.pwr_en_ch0.set_high() } } - pub fn pwr_disengage(){ + pub fn pwr_off(){ if let Some(ref mut wdg ) = LdAnalogWdg::get() { + wdg.alarm_status.pwr_engaged = false; wdg.phy.pwr_en_ch0.set_low() } } + + fn power_excursion_handler(){ + if let Some(ref mut wdg ) = LdAnalogWdg::get() { + let sample = wdg.pac.dr.read().data().bits(); + LdAnalogWdg::pwr_off(); + wdg.alarm_status.pwr_excursion = true; + wdg.alarm_status.v_tripped = LdAnalogWdg::convert_sample_to_volt(sample); + } + } } #[interrupt] fn ADC(){ cortex_m::interrupt::free(|_| { - LdAnalogWdg::set_alarm(); - LdAnalogWdg::pwr_disengage(); + LdAnalogWdg::power_excursion_handler(); // Disable interrupt to avoid getting stuck in infinite interrupt loop LdAnalogWdg::disable_watchdog_interrupt(); LdAnalogWdg::clear_interrupt_bit(); diff --git a/src/laser_diode/laser_diode.rs b/src/laser_diode/laser_diode.rs index 1bd8560..6ce996f 100644 --- a/src/laser_diode/laser_diode.rs +++ b/src/laser_diode/laser_diode.rs @@ -72,7 +72,7 @@ impl LdDrive{ } pub fn setup(&mut self) { - LdAnalogWdg::pwr_disengage(); + LdAnalogWdg::pwr_off(); self.ld_set_i(ElectricCurrent::new::(0.0)); self.ld_short(); } @@ -94,15 +94,15 @@ impl LdDrive{ } pub fn power_up(&mut self){ - LdAnalogWdg::pwr_engage(); + LdAnalogWdg::pwr_on_and_arm_protection(); } pub fn power_down(&mut self){ - LdAnalogWdg::pwr_disengage(); + LdAnalogWdg::pwr_off(); } pub fn get_pd_i(&mut self) -> ElectricCurrent { - LdAnalogWdg::get_pd_v() * Settings::PD_MON_TRANSCONDUCTANCE + LdAnalogWdg::get_status().v * Settings::PD_MON_TRANSCONDUCTANCE } pub fn ld_set_i(&mut self, i: ElectricCurrent) -> ElectricCurrent { @@ -113,28 +113,28 @@ impl LdDrive{ } // Set the calibrated VDDA value obtained from ADC1 calibration - pub fn set_pd_mon_calibrated_vdda(val_cal: u32) { + pub fn set_pd_mon_calibrated_vdda(&mut self, val_cal: u32) { LdAnalogWdg::set_calibrated_vdda(val_cal) } - pub fn pd_mon_status() -> analog_wdg::AlarmStatus { - LdAnalogWdg::get_alarm_status() + pub fn pd_mon_status(&mut self) -> analog_wdg::Status { + LdAnalogWdg::get_status() } pub fn pd_mon_clear_alarm(&mut self) { LdAnalogWdg::clear_alarm_status(); } - pub fn pd_mon_engage(){ - LdAnalogWdg::enable_watchdog_interrupt() - } - - pub fn pd_mon_disengage(){ - LdAnalogWdg::disable_watchdog_interrupt() - } - - pub fn set_ld_power_limit(pwr_limit: Power){ - // LdAnalogWdg::set_htr(convert pwr_limit to raw adc code) + pub fn set_ld_power_limit(&mut self, pwr_limit: Power){ + // LdAnalogWdg::set_trigger_threshold_v(convert pwr_limit to raw adc code) unimplemented!() } -} \ No newline at end of file + + pub fn set_pd_i_limit(&mut self, i: ElectricCurrent){ + LdAnalogWdg::set_trigger_threshold_v(i / Settings::PD_MON_TRANSCONDUCTANCE); + } + + pub fn set_pd_v_limit(&mut self, v: ElectricPotential){ + LdAnalogWdg::set_trigger_threshold_v(v); + } +} diff --git a/src/main.rs b/src/main.rs index 716223f..e78e436 100644 --- a/src/main.rs +++ b/src/main.rs @@ -80,7 +80,10 @@ fn main() -> ! { info!("curr_vref: {:?}", volt_fmt.with(thermostat.get_vref())); info!("curr_tec_i: {:?}", amp_fmt.with(thermostat.get_tec_i())); info!("curr_tec_v: {:?}", volt_fmt.with(thermostat.get_tec_v())); + + info!("pd_mon_v: {:?}", volt_fmt.with(laser.pd_mon_status().v)); + info!("power_excursion: {:?}", laser.pd_mon_status().pwr_excursion); - sys_timer::sleep(10); + sys_timer::sleep(500); } } diff --git a/src/thermostat/max1968.rs b/src/thermostat/max1968.rs index e48dfc3..2f19718 100644 --- a/src/thermostat/max1968.rs +++ b/src/thermostat/max1968.rs @@ -117,7 +117,8 @@ impl MAX1968 { let config = AdcConfig::default() .clock(config::Clock::Pclk2_div_8) .default_sample_time(config::SampleTime::Cycles_480); - let mut pins_adc = Adc::adc1(adc1, true, config); + // Do not set reset RCCs as it causes other ADCs' clock to be disabled + let mut pins_adc = Adc::adc1(adc1, false, config); pins_adc.calibrate(); let config = AdcConfig::default()