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;
+ }
+}