From 4c8ee8786f4b0e9307189c032c33beb00bb159cf Mon Sep 17 00:00:00 2001 From: Gabriel Smith Date: Fri, 3 Jan 2025 10:41:04 -0500 Subject: [PATCH] stm32: Implement reads of DTS peripheral Only PCLK-driven operation is supported. --- embassy-stm32/src/dts/mod.rs | 239 ++++++++++++++++++++++++++++++++ embassy-stm32/src/dts/tsel.rs | 51 +++++++ embassy-stm32/src/lib.rs | 2 + examples/stm32h5/src/bin/dts.rs | 75 ++++++++++ 4 files changed, 367 insertions(+) create mode 100644 embassy-stm32/src/dts/mod.rs create mode 100644 embassy-stm32/src/dts/tsel.rs create mode 100644 examples/stm32h5/src/bin/dts.rs diff --git a/embassy-stm32/src/dts/mod.rs b/embassy-stm32/src/dts/mod.rs new file mode 100644 index 0000000000..58d7cf8412 --- /dev/null +++ b/embassy-stm32/src/dts/mod.rs @@ -0,0 +1,239 @@ +//! Digital Temperature Sensor (DTS) + +use core::future::poll_fn; +use core::sync::atomic::{compiler_fence, Ordering}; +use core::task::Poll; + +use embassy_hal_internal::{into_ref, PeripheralRef}; +use embassy_sync::waitqueue::AtomicWaker; + +use crate::interrupt::InterruptExt; +use crate::peripherals::DTS; +use crate::time::Hertz; +use crate::{interrupt, pac, rcc, Peripheral}; + +mod tsel; +pub use tsel::TriggerSel; + +#[allow(missing_docs)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum SampleTime { + ClockCycles1 = 1, + ClockCycles2 = 2, + ClockCycles3 = 3, + ClockCycles4 = 4, + ClockCycles5 = 5, + ClockCycles6 = 6, + ClockCycles7 = 7, + ClockCycles8 = 8, + ClockCycles9 = 9, + ClockCycles10 = 10, + ClockCycles11 = 11, + ClockCycles12 = 12, + ClockCycles13 = 13, + ClockCycles14 = 14, + ClockCycles15 = 15, +} + +#[non_exhaustive] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// Config +pub struct Config { + /// Sample time + pub sample_time: SampleTime, + /// Trigger selection + pub trigger: TriggerSel, +} + +impl Default for Config { + fn default() -> Self { + Self { + sample_time: SampleTime::ClockCycles1, + trigger: TriggerSel::Software, + } + } +} + +/// The read-only factory calibration values used for converting a +/// measurement to a temperature. +#[derive(Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct FactoryCalibration { + /// The calibration temperature in degrees Celsius. + pub t0: u8, + /// The frequency at the calibration temperature. + pub fmt0: Hertz, + /// The ramp coefficient in Hertz per degree Celsius. + pub ramp_coeff: u16, +} + +const MAX_DTS_CLK_FREQ: Hertz = Hertz::mhz(1); + +/// Digital temperature sensor driver. +pub struct Dts<'d> { + _peri: PeripheralRef<'d, DTS>, +} + +static WAKER: AtomicWaker = AtomicWaker::new(); + +impl<'d> Dts<'d> { + /// Create a new temperature sensor driver. + pub fn new( + _peri: impl Peripheral

+ 'd, + _irq: impl interrupt::typelevel::Binding + 'd, + config: Config, + ) -> Self { + into_ref!(_peri); + rcc::enable_and_reset::(); + + let prescaler = rcc::frequency::() / MAX_DTS_CLK_FREQ; + + if prescaler > 127 { + panic!("DTS PCLK frequency must be less than 127 MHz."); + } + + Self::regs().cfgr1().modify(|w| { + w.set_refclk_sel(false); + w.set_hsref_clk_div(prescaler as u8); + w.set_q_meas_opt(false); + // Software trigger + w.set_intrig_sel(0); + w.set_smp_time(config.sample_time as u8); + w.set_intrig_sel(config.trigger as u8); + w.set_start(true); + w.set_en(true); + }); + + interrupt::DTS.unpend(); + unsafe { interrupt::DTS.enable() }; + + Self { _peri } + } + + /// Reconfigure the driver. + pub fn set_config(&mut self, config: &Config) { + Self::regs().cfgr1().modify(|w| { + w.set_smp_time(config.sample_time as u8); + w.set_intrig_sel(config.trigger as u8); + }); + } + + /// Get the read-only factory calibration values used for converting a + /// measurement to a temperature. + pub fn factory_calibration() -> FactoryCalibration { + let t0valr1 = Self::regs().t0valr1().read(); + let t0 = match t0valr1.t0() { + 0 => 30, + 1 => 130, + _ => unimplemented!(), + }; + let fmt0 = Hertz::hz(t0valr1.fmt0() as u32 * 100); + + let ramp_coeff = Self::regs().rampvalr().read().ramp_coeff(); + + FactoryCalibration { t0, fmt0, ramp_coeff } + } + + /// Perform an asynchronous temperature measurement. The returned future can + /// be awaited to obtain the measurement. + /// + /// The future returned waits for the next measurement to complete. + /// + /// # Example + /// + /// ```no_run + /// use embassy_stm32::{bind_interrupts, dts}; + /// use embassy_stm32::dts::Dts; + /// + /// bind_interrupts!(struct Irqs { + /// DTS => temp::InterruptHandler; + /// }); + /// + /// # async { + /// # let p: embassy_stm32::Peripherals = todo!(); + /// let mut dts = Dts::new(p.DTS, Irqs, Default::default()); + /// let v: u16 = dts.read().await; + /// # }; + /// ``` + pub async fn read(&mut self) -> u16 { + let r = Self::regs(); + + r.itenr().modify(|w| w.set_iteen(true)); + + poll_fn(|cx| { + WAKER.register(cx.waker()); + if r.itenr().read().iteen() { + Poll::Pending + } else { + Poll::Ready(r.dr().read().mfreq()) + } + }) + .await + } + + /// Returns the last measurement made, if any. + /// + /// There is no guarantee that the measurement is recent or that a + /// measurement has ever completed. + pub fn read_immediate(&mut self) -> u16 { + Self::regs().dr().read().mfreq() + } + + fn regs() -> pac::dts::Dts { + pac::DTS + } +} + +impl<'d> Drop for Dts<'d> { + fn drop(&mut self) { + Self::regs().cfgr1().modify(|w| w.set_en(false)); + rcc::disable::(); + } +} + +/// Interrupt handler. +pub struct InterruptHandler { + _private: (), +} + +impl interrupt::typelevel::Handler for InterruptHandler { + unsafe fn on_interrupt() { + let r = pac::DTS; + let (sr, itenr) = (r.sr().read(), r.itenr().read()); + + if (itenr.iteen() && sr.itef()) || (itenr.aiteen() && sr.aitef()) { + r.itenr().modify(|w| { + w.set_iteen(false); + w.set_aiteen(false); + }); + r.icifr().modify(|w| { + w.set_citef(true); + w.set_caitef(true); + }); + } else if (itenr.itlen() && sr.itlf()) || (itenr.aitlen() && sr.aitlf()) { + r.itenr().modify(|w| { + w.set_itlen(false); + w.set_aitlen(false); + }); + r.icifr().modify(|w| { + w.set_citlf(true); + w.set_caitlf(true); + }); + } else if (itenr.ithen() && sr.ithf()) || (itenr.aithen() && sr.aithf()) { + r.itenr().modify(|w| { + w.set_ithen(false); + w.set_aithen(false); + }); + r.icifr().modify(|w| { + w.set_cithf(true); + w.set_caithf(true); + }); + } else { + return; + } + + compiler_fence(Ordering::SeqCst); + WAKER.wake(); + } +} diff --git a/embassy-stm32/src/dts/tsel.rs b/embassy-stm32/src/dts/tsel.rs new file mode 100644 index 0000000000..99eab6dd8d --- /dev/null +++ b/embassy-stm32/src/dts/tsel.rs @@ -0,0 +1,51 @@ +/// Trigger selection for H5 +#[cfg(stm32h5)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + /// Software triggering. Performs continuous measurements. + Software = 0, + /// LPTIM1 CH1 + Lptim1 = 1, + /// LPTIM2 CH1 + Lptim2 = 2, + /// LPTIM3 CH1 + #[cfg(not(stm32h503))] + Lptim3 = 3, + /// EXTI13 + Exti13 = 4, +} + +/// Trigger selection for H7, except for H7R and H7S +#[cfg(stm32h7)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + /// Software triggering. Performs continuous measurements. + Software = 0, + /// LPTIM1 OUT + Lptim1 = 1, + /// LPTIM2 OUT + Lptim2 = 2, + /// LPTIM3 OUT + Lptim3 = 3, + /// EXTI13 + Exti13 = 4, +} + +/// Trigger selection for H7R and H7S +#[cfg(stm32h7rs)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum TriggerSel { + /// Software triggering. Performs continuous measurements. + Software = 0, + /// LPTIM4 OUT + Lptim4 = 1, + /// LPTIM2 CH1 + Lptim2 = 2, + /// LPTIM3 CH1 + Lptim3 = 3, + /// EXTI13 + Exti13 = 4, +} diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index d04199d05d..61da754c30 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -68,6 +68,8 @@ pub mod dac; pub mod dcmi; #[cfg(dsihost)] pub mod dsihost; +#[cfg(dts)] +pub mod dts; #[cfg(eth)] pub mod eth; #[cfg(feature = "exti")] diff --git a/examples/stm32h5/src/bin/dts.rs b/examples/stm32h5/src/bin/dts.rs new file mode 100644 index 0000000000..8c18fafea1 --- /dev/null +++ b/examples/stm32h5/src/bin/dts.rs @@ -0,0 +1,75 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::dts::{Dts, InterruptHandler, SampleTime}; +use embassy_stm32::peripherals::DTS; +use embassy_stm32::rcc::frequency; +use embassy_stm32::{bind_interrupts, dts, Config}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + DTS => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = Config::default(); + { + use embassy_stm32::rcc::*; + config.rcc.hsi = Some(HSIPrescaler::DIV1); + config.rcc.csi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: Some(PllDiv::DIV2), + divq: Some(PllDiv::DIV4), // SPI1 cksel defaults to pll1_q + divr: None, + }); + config.rcc.pll2 = Some(Pll { + source: PllSource::HSI, + prediv: PllPreDiv::DIV4, + mul: PllMul::MUL25, + divp: None, + divq: None, + divr: Some(PllDiv::DIV4), // 100mhz + }); + config.rcc.sys = Sysclk::PLL1_P; // 200 Mhz + config.rcc.ahb_pre = AHBPrescaler::DIV1; // 200 Mhz + config.rcc.apb1_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb2_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.apb3_pre = APBPrescaler::DIV2; // 100 Mhz + config.rcc.voltage_scale = VoltageScale::Scale1; + config.rcc.mux.adcdacsel = mux::Adcdacsel::PLL2_R; + } + let p = embassy_stm32::init(config); + + info!("Hello World!"); + + let mut config = dts::Config::default(); + config.sample_time = SampleTime::ClockCycles15; + let mut dts = Dts::new(p.DTS, Irqs, config); + + let cal = Dts::factory_calibration(); + let convert_to_celsius = |raw_temp: u16| { + let raw_temp = raw_temp as f32; + let sample_time = (config.sample_time as u8) as f32; + + let f = frequency::().0 as f32; + + let t0 = cal.t0 as f32; + let fmt0 = cal.fmt0.0 as f32; + let ramp_coeff = cal.ramp_coeff as f32; + + ((f * sample_time / raw_temp) - fmt0) / ramp_coeff + t0 + }; + + loop { + let temp = dts.read().await; + info!("Temp: {} degrees", convert_to_celsius(temp)); + Timer::after_millis(500).await; + } +}