Analog_Wdg: Finish Power Excursion Protection fns

- Verified to be working
This commit is contained in:
linuswck 2024-01-24 17:03:06 +08:00
parent 07ea733b34
commit 85b50bf824
5 changed files with 175 additions and 134 deletions

View File

@ -13,6 +13,7 @@ use stm32f4xx_hal::{
watchdog::IndependentWatchdog, watchdog::IndependentWatchdog,
}; };
use uom::si::electric_current::milliampere;
use uom::si::{electric_current::ampere, f64::ElectricCurrent}; use uom::si::{electric_current::ampere, f64::ElectricCurrent};
#[cfg(not(feature = "semihosting"))] #[cfg(not(feature = "semihosting"))]
@ -65,6 +66,7 @@ pub fn bootup(
laser.ld_open(); laser.ld_open();
laser.set_ld_drive_current_limit(ElectricCurrent::new::<ampere>(0.2)); laser.set_ld_drive_current_limit(ElectricCurrent::new::<ampere>(0.2));
laser.ld_set_i(ElectricCurrent::new::<ampere>(0.15)); laser.ld_set_i(ElectricCurrent::new::<ampere>(0.15));
laser.set_pd_i_limit(ElectricCurrent::new::<milliampere>(2.5));
laser.power_up(); laser.power_up();
let tec_driver = MAX1968::new(max1968_phy, perif.ADC1); let tec_driver = MAX1968::new(max1968_phy, perif.ADC1);
@ -76,6 +78,8 @@ pub fn bootup(
thermostat.calibrate_dac_value(); thermostat.calibrate_dac_value();
thermostat.set_i(ElectricCurrent::new::<ampere>(1.0)); thermostat.set_i(ElectricCurrent::new::<ampere>(1.0));
laser.set_pd_mon_calibrated_vdda(thermostat.get_calibrated_vdda());
let flash_store = flash_store::store(perif.FLASH); let flash_store = flash_store::store(perif.FLASH);
let mut wd = IndependentWatchdog::new(perif.IWDG); let mut wd = IndependentWatchdog::new(perif.IWDG);

View File

@ -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::{ use stm32f4xx_hal::{
pac::{ADC2, NVIC}, pac::{ADC2, NVIC},
gpio::{Analog, Output, PushPull, gpioa::PA3, gpiod::PD9}, gpio::{Analog, Output, PushPull, gpioa::PA3, gpiod::PD9},
interrupt, interrupt,
}; };
use uom::si::electric_potential::millivolt; use uom::si::{
use uom::si::f64::ElectricPotential; electric_potential::millivolt,
f64::ElectricPotential,
ratio::ratio
};
use crate::info;
// 12 bit Resolution // 12 bit Resolution
const MAX_SAMPLE: u16 = 4095; const MAX_SAMPLE: u16 = 4095;
pub type LdPwrEnPinType = PD9<Output<PushPull>>; pub type LdPwrEnPinType = PD9<Output<PushPull>>;
pub type PdMonAdcPinType = PA3<Analog>; pub type PdMonAdcPinType = PA3<Analog>;
const PD_MON_ADC_CH_ID: u8 = 0x03;
static mut ANALOG_WDG: Option<LdAnalogWdg> = None; static mut ANALOG_WDG: Option<LdAnalogWdg> = None;
pub struct LdAnalogWdgPhy { 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 _pd_mon_ch0: PdMonAdcPinType,
pub pwr_en_ch0: LdPwrEnPinType, pub pwr_en_ch0: LdPwrEnPinType,
} }
#[derive(Clone)] #[derive(Clone)]
pub struct AlarmStatus { pub struct Status {
alarm: bool, pub pwr_excursion: bool,
val: u16, 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),
}
}
} }
pub struct LdAnalogWdg { pub struct LdAnalogWdg {
pac: ADC2, pac: ADC2,
phy: LdAnalogWdgPhy, phy: LdAnalogWdgPhy,
alarm_status: AlarmStatus, alarm_status: Status,
//Calibrated VDDA in millivolt from Adc<ADC1>.calibrate() //Calibrated VDDA in millivolt from Adc<ADC1>.calibrate()
calibrated_vdda: u32, calibrated_vdda: u32,
} }
impl LdAnalogWdg { impl LdAnalogWdg {
/// ADC interrupt is disabled and continuous conversion is started by default /// ADC Analog Watchdog is configured to guard a single regular Adc channel on Pd Mon Pin.
pub fn setup(pac: ADC2, phy: LdAnalogWdgPhy){ /// ADC is configured to start continuous conversion without using DMA immediately.
unsafe { ANALOG_WDG = Some(LdAnalogWdg{ /// Interrupt is disabled by default.
pac: pac, pub fn setup(pac_adc: ADC2, mut phy: LdAnalogWdgPhy){
phy: phy, unsafe {
alarm_status: AlarmStatus { // All ADCs share the same reset interface.
alarm: false, // NOTE(unsafe) this reference will only be used for atomic writes with no side effects.
val: 0x0000, let rcc = &(*pac::RCC::ptr());
}, // Enable the ADC2 Clock
calibrated_vdda: 3300, 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 { unsafe {
if let Some(ref mut wdg ) = LdAnalogWdg::get() { ANALOG_WDG = Some(
wdg.pac.cr1.write(|w| w LdAnalogWdg {
// 12 Bit Resolution pac: pac_adc,
.res().twelve_bit() phy: phy,
// Enable Analog Watchdog on Single Regular Channel alarm_status: Status::default(),
.awden().enabled() calibrated_vdda: 3300,
.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)
);
}
} }
} }
@ -92,68 +135,53 @@ impl LdAnalogWdg {
unsafe { ANALOG_WDG.as_mut() } unsafe { ANALOG_WDG.as_mut() }
} }
/// This fn accepts the calibrated vdda value from Adc<ADC1>.calibrate() fn convert_sample_to_volt(sample :u16) -> ElectricPotential {
if let Some(ref mut wdg ) = LdAnalogWdg::get() {
return ElectricPotential::new::<millivolt>(((u32::from(sample) * wdg.calibrated_vdda) / u32::from(MAX_SAMPLE)) as f64)
}
ElectricPotential::new::<millivolt>(0.0)
}
pub fn set_trigger_threshold_v(htr: ElectricPotential){
if let Some(ref mut wdg ) = LdAnalogWdg::get() {
let code: u32 = ((htr / (ElectricPotential::new::<millivolt>(wdg.calibrated_vdda as f64))).get::<ratio>() * (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) { pub fn set_calibrated_vdda(val: u32) {
if let Some(ref mut wdg ) = LdAnalogWdg::get() { if let Some(ref mut wdg ) = LdAnalogWdg::get() {
wdg.calibrated_vdda = val; wdg.calibrated_vdda = val;
} }
} }
pub fn get_pd_v() -> ElectricPotential { pub fn get_status() -> Status {
if let Some(ref mut wdg ) = LdAnalogWdg::get() {
let sample = wdg.pac.dr.read().data().bits();
return ElectricPotential::new::<millivolt>(((u32::from(sample) * wdg.calibrated_vdda) / u32::from(MAX_SAMPLE)) as f64)
}
ElectricPotential::new::<millivolt>(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 {
if let Some(ref mut wdg ) = LdAnalogWdg::get() { 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() return wdg.alarm_status.clone()
} }
AlarmStatus { Status::default()
alarm: false, }
val: 0x0000,
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(){ pub fn clear_alarm_status(){
if let Some(ref mut wdg ) = LdAnalogWdg::get() { if let Some(ref mut wdg ) = LdAnalogWdg::get() {
wdg.alarm_status = AlarmStatus { wdg.alarm_status.pwr_excursion = false;
alarm: false, wdg.alarm_status.v_tripped = ElectricPotential::new::<millivolt>(0.0);
val: 0x0000,
};
} }
} }
/// Set ADC Watchdog Higher threshold register fn enable_watchdog_interrupt(){
/// 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(){
if let Some(ref mut wdg ) = LdAnalogWdg::get() { if let Some(ref mut wdg ) = LdAnalogWdg::get() {
wdg.pac.cr1.modify(|_, w| w wdg.pac.cr1.modify(|_, w| w
.awdie().set_bit() .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() { if let Some(ref mut wdg ) = LdAnalogWdg::get() {
wdg.pac.cr1.modify(|_, w| w wdg.pac.cr1.modify(|_, w| w
.awdie().clear_bit() .awdie().clear_bit()
@ -170,36 +198,41 @@ impl LdAnalogWdg {
} }
fn clear_interrupt_bit(){ fn clear_interrupt_bit(){
unsafe{ if let Some(ref mut wdg ) = LdAnalogWdg::get() {
NVIC::unmask(interrupt::ADC); wdg.pac.sr.modify(|_, w| w
.awd().clear_bit()
);
} }
} }
pub fn is_pwr_engaged() -> bool{ fn pwr_on(){
if let Some(ref mut wdg ) = LdAnalogWdg::get() {
return wdg.phy.pwr_en_ch0.is_set_high()
}
false
}
pub fn pwr_engage(){
if let Some(ref mut wdg ) = LdAnalogWdg::get() { if let Some(ref mut wdg ) = LdAnalogWdg::get() {
wdg.alarm_status.pwr_engaged = true;
wdg.phy.pwr_en_ch0.set_high() wdg.phy.pwr_en_ch0.set_high()
} }
} }
pub fn pwr_disengage(){ pub fn pwr_off(){
if let Some(ref mut wdg ) = LdAnalogWdg::get() { if let Some(ref mut wdg ) = LdAnalogWdg::get() {
wdg.alarm_status.pwr_engaged = false;
wdg.phy.pwr_en_ch0.set_low() 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] #[interrupt]
fn ADC(){ fn ADC(){
cortex_m::interrupt::free(|_| { cortex_m::interrupt::free(|_| {
LdAnalogWdg::set_alarm(); LdAnalogWdg::power_excursion_handler();
LdAnalogWdg::pwr_disengage();
// Disable interrupt to avoid getting stuck in infinite interrupt loop // Disable interrupt to avoid getting stuck in infinite interrupt loop
LdAnalogWdg::disable_watchdog_interrupt(); LdAnalogWdg::disable_watchdog_interrupt();
LdAnalogWdg::clear_interrupt_bit(); LdAnalogWdg::clear_interrupt_bit();

View File

@ -72,7 +72,7 @@ impl LdDrive{
} }
pub fn setup(&mut self) { pub fn setup(&mut self) {
LdAnalogWdg::pwr_disengage(); LdAnalogWdg::pwr_off();
self.ld_set_i(ElectricCurrent::new::<milliampere>(0.0)); self.ld_set_i(ElectricCurrent::new::<milliampere>(0.0));
self.ld_short(); self.ld_short();
} }
@ -94,15 +94,15 @@ impl LdDrive{
} }
pub fn power_up(&mut self){ pub fn power_up(&mut self){
LdAnalogWdg::pwr_engage(); LdAnalogWdg::pwr_on_and_arm_protection();
} }
pub fn power_down(&mut self){ pub fn power_down(&mut self){
LdAnalogWdg::pwr_disengage(); LdAnalogWdg::pwr_off();
} }
pub fn get_pd_i(&mut self) -> ElectricCurrent { 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 { 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 // 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) LdAnalogWdg::set_calibrated_vdda(val_cal)
} }
pub fn pd_mon_status() -> analog_wdg::AlarmStatus { pub fn pd_mon_status(&mut self) -> analog_wdg::Status {
LdAnalogWdg::get_alarm_status() LdAnalogWdg::get_status()
} }
pub fn pd_mon_clear_alarm(&mut self) { pub fn pd_mon_clear_alarm(&mut self) {
LdAnalogWdg::clear_alarm_status(); LdAnalogWdg::clear_alarm_status();
} }
pub fn pd_mon_engage(){ pub fn set_ld_power_limit(&mut self, pwr_limit: Power){
LdAnalogWdg::enable_watchdog_interrupt() // LdAnalogWdg::set_trigger_threshold_v(convert pwr_limit to raw adc code)
}
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)
unimplemented!() unimplemented!()
} }
}
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);
}
}

View File

@ -80,7 +80,10 @@ fn main() -> ! {
info!("curr_vref: {:?}", volt_fmt.with(thermostat.get_vref())); info!("curr_vref: {:?}", volt_fmt.with(thermostat.get_vref()));
info!("curr_tec_i: {:?}", amp_fmt.with(thermostat.get_tec_i())); info!("curr_tec_i: {:?}", amp_fmt.with(thermostat.get_tec_i()));
info!("curr_tec_v: {:?}", volt_fmt.with(thermostat.get_tec_v())); 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);
} }
} }

View File

@ -117,7 +117,8 @@ impl MAX1968 {
let config = AdcConfig::default() let config = AdcConfig::default()
.clock(config::Clock::Pclk2_div_8) .clock(config::Clock::Pclk2_div_8)
.default_sample_time(config::SampleTime::Cycles_480); .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(); pins_adc.calibrate();
let config = AdcConfig::default() let config = AdcConfig::default()