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, f32::ElectricPotential, ratio::ratio }; // 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 LD_PWR_EXC_PROTECTOR: Option = None; pub struct LdPwrExcProtectorPhy { // 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 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 LdPwrExcProtector { pac: ADC2, phy: LdPwrExcProtectorPhy, alarm_status: Status, calibrated_vdda: u32, } impl LdPwrExcProtector { /// 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: LdPwrExcProtectorPhy){ 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 channels to have fastest sampling interval pac_adc.smpr1.reset(); pac_adc.smpr2.reset(); // Set the higher threshold to be max value initially pac_adc.htr.write(|w| w.ht().variant(MAX_SAMPLE)); // Set the lower 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() ); phy.pwr_en_ch0.set_low(); unsafe { LD_PWR_EXC_PROTECTOR = Some( LdPwrExcProtector { pac: pac_adc, phy: phy, alarm_status: Status::default(), calibrated_vdda: 3300, } ); } } fn get() -> Option<&'static mut Self> { unsafe { LD_PWR_EXC_PROTECTOR.as_mut() } } fn convert_sample_to_volt(sample :u16) -> ElectricPotential { if let Some(ref mut wdg ) = LdPwrExcProtector::get() { return ElectricPotential::new::(((u32::from(sample) * wdg.calibrated_vdda) / u32::from(MAX_SAMPLE)) as f32) } ElectricPotential::new::(0.0) } pub fn set_trigger_threshold_v(htr: ElectricPotential){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { let code: u32 = ((htr / (ElectricPotential::new::(wdg.calibrated_vdda as f32))).get::() * (MAX_SAMPLE as f32)) as u32; wdg.pac.htr.write(|w| unsafe {w.bits(code)}); } } pub fn set_calibrated_vdda(val: u32) { if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.calibrated_vdda = val; } } pub fn get_status() -> Status { if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.alarm_status.v = LdPwrExcProtector::convert_sample_to_volt(wdg.pac.dr.read().data().bits()); return wdg.alarm_status.clone() } Status::default() } pub fn pwr_on_and_arm_protection(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.alarm_status = Status::default(); LdPwrExcProtector::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. LdPwrExcProtector::enable_watchdog_interrupt(); } } pub fn clear_alarm_status(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.alarm_status.pwr_excursion = false; wdg.alarm_status.v_tripped = ElectricPotential::new::(0.0); } } fn enable_watchdog_interrupt(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.pac.cr1.modify(|_, w| w .awdie().set_bit() ); } } fn disable_watchdog_interrupt(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.pac.cr1.modify(|_, w| w .awdie().clear_bit() ); } } fn clear_interrupt_bit(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.pac.sr.modify(|_, w| w .awd().clear_bit() ); } } fn pwr_on(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.alarm_status.pwr_engaged = true; wdg.phy.pwr_en_ch0.set_high() } } pub fn pwr_off(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { wdg.alarm_status.pwr_engaged = false; wdg.phy.pwr_en_ch0.set_low() } } fn pwr_excursion_handler(){ if let Some(ref mut wdg ) = LdPwrExcProtector::get() { let sample = wdg.pac.dr.read().data().bits(); LdPwrExcProtector::pwr_off(); wdg.alarm_status.pwr_excursion = true; wdg.alarm_status.v_tripped = LdPwrExcProtector::convert_sample_to_volt(sample); } } } #[interrupt] fn ADC(){ cortex_m::interrupt::free(|_| { LdPwrExcProtector::pwr_excursion_handler(); // Disable interrupt to avoid getting stuck in infinite loop LdPwrExcProtector::disable_watchdog_interrupt(); LdPwrExcProtector::clear_interrupt_bit(); } ) }