2024-04-23 17:09:26 +08:00
|
|
|
use stm32f4xx_hal::{gpio::{gpioa::PA3, gpiod::PD9, Analog, Output, PushPull},
|
|
|
|
interrupt, pac,
|
|
|
|
pac::{ADC3, NVIC},
|
|
|
|
rcc::Enable};
|
|
|
|
use uom::si::{electric_potential::millivolt, f32::ElectricPotential, ratio::ratio};
|
2024-01-24 11:47:15 +08:00
|
|
|
|
|
|
|
// 12 bit Resolution
|
|
|
|
const MAX_SAMPLE: u16 = 4095;
|
|
|
|
pub type LdPwrEnPinType = PD9<Output<PushPull>>;
|
|
|
|
pub type PdMonAdcPinType = PA3<Analog>;
|
2024-01-24 17:03:06 +08:00
|
|
|
const PD_MON_ADC_CH_ID: u8 = 0x03;
|
2024-01-24 11:47:15 +08:00
|
|
|
|
2024-01-26 11:43:53 +08:00
|
|
|
static mut LD_PWR_EXC_PROTECTOR: Option<LdPwrExcProtector> = None;
|
2024-01-24 11:47:15 +08:00
|
|
|
|
2024-01-26 11:43:53 +08:00
|
|
|
pub struct LdPwrExcProtectorPhy {
|
2024-01-24 17:03:06 +08:00
|
|
|
// To make sure Pd Mon Pin is configured to Analog mode
|
2024-04-23 17:09:26 +08:00
|
|
|
pub _pd_mon_ch0: PdMonAdcPinType,
|
|
|
|
pub pwr_en_ch0: LdPwrEnPinType,
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2024-01-24 17:03:06 +08:00
|
|
|
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::<millivolt>(0.0),
|
|
|
|
pwr_engaged: false,
|
|
|
|
v: ElectricPotential::new::<millivolt>(0.0),
|
|
|
|
}
|
|
|
|
}
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
2024-01-26 12:56:53 +08:00
|
|
|
|
2024-01-26 11:43:53 +08:00
|
|
|
pub struct LdPwrExcProtector {
|
2024-02-29 16:47:03 +08:00
|
|
|
pac: ADC3,
|
2024-01-26 11:43:53 +08:00
|
|
|
phy: LdPwrExcProtectorPhy,
|
2024-01-24 17:03:06 +08:00
|
|
|
alarm_status: Status,
|
2024-04-23 17:09:26 +08:00
|
|
|
calibrated_vdda: u32,
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
|
2024-01-26 11:43:53 +08:00
|
|
|
impl LdPwrExcProtector {
|
2024-01-24 17:03:06 +08:00
|
|
|
/// 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.
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn setup(pac_adc: ADC3, mut phy: LdPwrExcProtectorPhy) {
|
2024-01-24 17:03:06 +08:00
|
|
|
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());
|
2024-02-29 16:47:03 +08:00
|
|
|
// Enable the ADC3 Clock
|
|
|
|
pac::ADC3::enable(rcc);
|
2024-01-24 17:03:06 +08:00
|
|
|
// Enable ADC Interrupt
|
|
|
|
NVIC::unmask(interrupt::ADC);
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
|
2024-01-24 17:03:06 +08:00
|
|
|
pac_adc.cr1.reset();
|
|
|
|
pac_adc.cr2.reset();
|
|
|
|
pac_adc.sqr1.reset();
|
|
|
|
pac_adc.sqr2.reset();
|
|
|
|
pac_adc.sqr3.reset();
|
2024-04-23 17:09:26 +08:00
|
|
|
|
|
|
|
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()
|
|
|
|
});
|
2024-01-24 17:03:06 +08:00
|
|
|
// Set the Conversion Sequence to include Pd Mon Pin
|
2024-04-23 17:09:26 +08:00
|
|
|
pac_adc.sqr3.write(|w| w.sq1().variant(PD_MON_ADC_CH_ID));
|
2024-01-26 12:56:53 +08:00
|
|
|
// Set all sampling channels to have fastest sampling interval
|
2024-01-24 17:03:06 +08:00
|
|
|
pac_adc.smpr1.reset();
|
|
|
|
pac_adc.smpr2.reset();
|
2024-04-23 17:09:26 +08:00
|
|
|
|
2024-01-26 12:56:53 +08:00
|
|
|
// Set the higher threshold to be max value initially
|
2024-01-24 17:03:06 +08:00
|
|
|
pac_adc.htr.write(|w| w.ht().variant(MAX_SAMPLE));
|
2024-01-26 12:56:53 +08:00
|
|
|
// Set the lower threshold to be min value initially
|
2024-01-24 17:03:06 +08:00
|
|
|
pac_adc.ltr.write(|w| w.lt().variant(0));
|
|
|
|
|
|
|
|
// SWStart should only be set when ADON = 1. Otherwise no conversion is launched.
|
2024-04-23 17:09:26 +08:00
|
|
|
pac_adc.cr2.modify(|_, w| w.swstart().set_bit());
|
2024-01-24 17:03:06 +08:00
|
|
|
|
|
|
|
phy.pwr_en_ch0.set_low();
|
|
|
|
|
2024-01-24 11:47:15 +08:00
|
|
|
unsafe {
|
2024-04-23 17:09:26 +08:00
|
|
|
LD_PWR_EXC_PROTECTOR = Some(LdPwrExcProtector {
|
|
|
|
pac: pac_adc,
|
|
|
|
phy: phy,
|
|
|
|
alarm_status: Status::default(),
|
|
|
|
calibrated_vdda: 3300,
|
|
|
|
});
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn get() -> Option<&'static mut Self> {
|
2024-01-26 11:43:53 +08:00
|
|
|
unsafe { LD_PWR_EXC_PROTECTOR.as_mut() }
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn convert_sample_to_volt(sample: u16) -> ElectricPotential {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
|
|
|
return ElectricPotential::new::<millivolt>(
|
|
|
|
((u32::from(sample) * wdg.calibrated_vdda) / u32::from(MAX_SAMPLE)) as f32,
|
|
|
|
);
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
ElectricPotential::new::<millivolt>(0.0)
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn set_trigger_threshold_v(htr: ElectricPotential) {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
|
|
|
let code: u32 = ((htr / (ElectricPotential::new::<millivolt>(wdg.calibrated_vdda as f32))).get::<ratio>()
|
|
|
|
* (MAX_SAMPLE as f32)) as u32;
|
|
|
|
wdg.pac.htr.write(|w| unsafe { w.bits(code) });
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-24 17:03:06 +08:00
|
|
|
pub fn set_calibrated_vdda(val: u32) {
|
2024-04-23 17:09:26 +08:00
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
2024-01-24 17:03:06 +08:00
|
|
|
wdg.calibrated_vdda = val;
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-24 17:03:06 +08:00
|
|
|
pub fn get_status() -> Status {
|
2024-04-23 17:09:26 +08:00
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
2024-01-26 11:43:53 +08:00
|
|
|
wdg.alarm_status.v = LdPwrExcProtector::convert_sample_to_volt(wdg.pac.dr.read().data().bits());
|
2024-04-23 17:09:26 +08:00
|
|
|
return wdg.alarm_status.clone();
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
2024-01-24 17:03:06 +08:00
|
|
|
Status::default()
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn pwr_on_and_arm_protection() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
2024-01-24 17:03:06 +08:00
|
|
|
wdg.alarm_status = Status::default();
|
2024-01-26 11:43:53 +08:00
|
|
|
LdPwrExcProtector::pwr_on();
|
2024-01-24 17:03:06 +08:00
|
|
|
// 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.
|
2024-01-26 11:43:53 +08:00
|
|
|
LdPwrExcProtector::enable_watchdog_interrupt();
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn clear_alarm_status() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
2024-01-24 17:03:06 +08:00
|
|
|
wdg.alarm_status.pwr_excursion = false;
|
|
|
|
wdg.alarm_status.v_tripped = ElectricPotential::new::<millivolt>(0.0);
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn enable_watchdog_interrupt() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
|
|
|
wdg.pac.cr1.modify(|_, w| w.awdie().set_bit());
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn disable_watchdog_interrupt() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
|
|
|
wdg.pac.cr1.modify(|_, w| w.awdie().clear_bit());
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn clear_interrupt_bit() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
|
|
|
wdg.pac.sr.modify(|_, w| w.awd().clear_bit());
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn pwr_on() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
2024-01-24 17:03:06 +08:00
|
|
|
wdg.alarm_status.pwr_engaged = true;
|
|
|
|
wdg.phy.pwr_en_ch0.set_high()
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
pub fn pwr_off() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
2024-01-24 17:03:06 +08:00
|
|
|
wdg.alarm_status.pwr_engaged = false;
|
|
|
|
wdg.phy.pwr_en_ch0.set_low()
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-23 17:09:26 +08:00
|
|
|
fn pwr_excursion_handler() {
|
|
|
|
if let Some(ref mut wdg) = LdPwrExcProtector::get() {
|
2024-01-24 17:03:06 +08:00
|
|
|
let sample = wdg.pac.dr.read().data().bits();
|
2024-01-26 11:43:53 +08:00
|
|
|
LdPwrExcProtector::pwr_off();
|
2024-01-24 17:03:06 +08:00
|
|
|
wdg.alarm_status.pwr_excursion = true;
|
2024-01-26 11:43:53 +08:00
|
|
|
wdg.alarm_status.v_tripped = LdPwrExcProtector::convert_sample_to_volt(sample);
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[interrupt]
|
2024-04-23 17:09:26 +08:00
|
|
|
fn ADC() {
|
2024-01-24 11:47:15 +08:00
|
|
|
cortex_m::interrupt::free(|_| {
|
2024-04-23 17:09:26 +08:00
|
|
|
LdPwrExcProtector::pwr_excursion_handler();
|
|
|
|
// Disable interrupt to avoid getting stuck in infinite loop
|
|
|
|
LdPwrExcProtector::disable_watchdog_interrupt();
|
|
|
|
LdPwrExcProtector::clear_interrupt_bit();
|
|
|
|
})
|
2024-01-24 11:47:15 +08:00
|
|
|
}
|