//! Provide access to the STM32 digital-to-analog converter (DAC). #![macro_use] use core::marker::PhantomData; use embassy_hal_internal::{into_ref, PeripheralRef}; use crate::dma::NoDma; #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] use crate::pac::dac; use crate::rcc::RccPeripheral; use crate::{peripherals, Peripheral}; mod tsel; pub use tsel::TriggerSel; /// Operating mode for DAC channel #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum Mode { /// Normal mode, channel is connected to external pin with buffer enabled. NormalExternalBuffered, /// Normal mode, channel is connected to external pin and internal peripherals /// with buffer enabled. NormalBothBuffered, /// Normal mode, channel is connected to external pin with buffer disabled. NormalExternalUnbuffered, /// Normal mode, channel is connected to internal peripherals with buffer disabled. NormalInternalUnbuffered, /// Sample-and-hold mode, channel is connected to external pin with buffer enabled. SampleHoldExternalBuffered, /// Sample-and-hold mode, channel is connected to external pin and internal peripherals /// with buffer enabled. SampleHoldBothBuffered, /// Sample-and-hold mode, channel is connected to external pin and internal peripherals /// with buffer disabled. SampleHoldBothUnbuffered, /// Sample-and-hold mode, channel is connected to internal peripherals with buffer disabled. SampleHoldInternalUnbuffered, } #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] impl Mode { fn mode(&self) -> dac::vals::Mode { match self { Mode::NormalExternalBuffered => dac::vals::Mode::NORMAL_EXT_BUFEN, Mode::NormalBothBuffered => dac::vals::Mode::NORMAL_EXT_INT_BUFEN, Mode::NormalExternalUnbuffered => dac::vals::Mode::NORMAL_EXT_BUFDIS, Mode::NormalInternalUnbuffered => dac::vals::Mode::NORMAL_INT_BUFDIS, Mode::SampleHoldExternalBuffered => dac::vals::Mode::SAMPHOLD_EXT_BUFEN, Mode::SampleHoldBothBuffered => dac::vals::Mode::SAMPHOLD_EXT_INT_BUFEN, Mode::SampleHoldBothUnbuffered => dac::vals::Mode::SAMPHOLD_EXT_INT_BUFDIS, Mode::SampleHoldInternalUnbuffered => dac::vals::Mode::SAMPHOLD_INT_BUFDIS, } } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Single 8 or 12 bit value that can be output by the DAC. /// /// 12-bit values outside the permitted range are silently truncated. pub enum Value { // 8 bit value Bit8(u8), // 12 bit value stored in a u16, left-aligned Bit12Left(u16), // 12 bit value stored in a u16, right-aligned Bit12Right(u16), } #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Dual 8 or 12 bit values that can be output by the DAC channels 1 and 2 simultaneously. /// /// 12-bit values outside the permitted range are silently truncated. pub enum DualValue { // 8 bit value Bit8(u8, u8), // 12 bit value stored in a u16, left-aligned Bit12Left(u16, u16), // 12 bit value stored in a u16, right-aligned Bit12Right(u16, u16), } #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Array variant of [`Value`]. pub enum ValueArray<'a> { // 8 bit values Bit8(&'a [u8]), // 12 bit value stored in a u16, left-aligned Bit12Left(&'a [u16]), // 12 bit values stored in a u16, right-aligned Bit12Right(&'a [u16]), } /// Driver for a single DAC channel. /// /// If you want to use both channels, either together or independently, /// create a [`Dac`] first and use it to access each channel. pub struct DacChannel<'d, T: Instance, const N: u8, DMA = NoDma> { phantom: PhantomData<&'d mut T>, #[allow(unused)] dma: PeripheralRef<'d, DMA>, } pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>; pub type DacCh2<'d, T, DMA = NoDma> = DacChannel<'d, T, 2, DMA>; impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { const IDX: usize = (N - 1) as usize; /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral. /// /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. /// /// The channel is enabled on creation and begins to drive the output pin. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will /// disable the channel; you must re-enable it with `enable()`. /// /// By default, triggering is disabled, but it can be enabled using /// [`DacChannel::set_trigger()`]. pub fn new( _peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd, pin: impl Peripheral

+ crate::gpio::sealed::Pin> + 'd, ) -> Self { into_ref!(dma, pin); pin.set_as_analog(); T::enable_and_reset(); let mut dac = Self { phantom: PhantomData, dma, }; #[cfg(any(dac_v5, dac_v6, dac_v7))] dac.set_hfsel(); dac.enable(); dac } /// Create a new `DacChannel` instance where the external output pin is not used, /// so the DAC can only be used to generate internal signals. /// The GPIO pin is therefore available to be used for other functions. /// /// The channel is set to [`Mode::NormalInternalUnbuffered`] and enabled on creation. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the /// channel; you must re-enable it with `enable()`. /// /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. /// /// By default, triggering is disabled, but it can be enabled using /// [`DacChannel::set_trigger()`]. #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] pub fn new_internal(_peri: impl Peripheral

+ 'd, dma: impl Peripheral

+ 'd) -> Self { into_ref!(dma); T::enable_and_reset(); let mut dac = Self { phantom: PhantomData, dma, }; #[cfg(any(dac_v5, dac_v6, dac_v7))] dac.set_hfsel(); dac.set_mode(Mode::NormalInternalUnbuffered); dac.enable(); dac } /// Enable or disable this channel. pub fn set_enable(&mut self, on: bool) { critical_section::with(|_| { T::regs().cr().modify(|reg| { reg.set_en(Self::IDX, on); }); }); } /// Enable this channel. pub fn enable(&mut self) { self.set_enable(true) } /// Disable this channel. pub fn disable(&mut self) { self.set_enable(false) } /// Set the trigger source for this channel. /// /// This method disables the channel, so you may need to re-enable afterwards. pub fn set_trigger(&mut self, source: TriggerSel) { critical_section::with(|_| { T::regs().cr().modify(|reg| { reg.set_en(Self::IDX, false); reg.set_tsel(Self::IDX, source as u8); }); }); } /// Enable or disable triggering for this channel. pub fn set_triggering(&mut self, on: bool) { critical_section::with(|_| { T::regs().cr().modify(|reg| { reg.set_ten(Self::IDX, on); }); }); } /// Software trigger this channel. pub fn trigger(&mut self) { T::regs().swtrigr().write(|reg| { reg.set_swtrig(Self::IDX, true); }); } /// Set mode of this channel. /// /// This method disables the channel, so you may need to re-enable afterwards. #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] pub fn set_mode(&mut self, mode: Mode) { critical_section::with(|_| { T::regs().cr().modify(|reg| { reg.set_en(Self::IDX, false); }); T::regs().mcr().modify(|reg| { reg.set_mode(Self::IDX, mode.mode()); }); }); } /// Write a new value to this channel. /// /// If triggering is not enabled, the new value is immediately output; otherwise, /// it will be output after the next trigger. pub fn set(&mut self, value: Value) { match value { Value::Bit8(v) => T::regs().dhr8r(Self::IDX).write(|reg| reg.set_dhr(v)), Value::Bit12Left(v) => T::regs().dhr12l(Self::IDX).write(|reg| reg.set_dhr(v)), Value::Bit12Right(v) => T::regs().dhr12r(Self::IDX).write(|reg| reg.set_dhr(v)), } } /// Read the current output value of the DAC. pub fn read(&self) -> u16 { T::regs().dor(Self::IDX).read().dor() } /// Set HFSEL as appropriate for the current peripheral clock frequency. #[cfg(dac_v5)] fn set_hfsel(&mut self) { if T::frequency() >= crate::time::mhz(80) { critical_section::with(|_| { T::regs().cr().modify(|reg| { reg.set_hfsel(true); }); }); } } /// Set HFSEL as appropriate for the current peripheral clock frequency. #[cfg(any(dac_v6, dac_v7))] fn set_hfsel(&mut self) { if T::frequency() >= crate::time::mhz(160) { critical_section::with(|_| { T::regs().mcr().modify(|reg| { reg.set_hfsel(0b10); }); }); } else if T::frequency() >= crate::time::mhz(80) { critical_section::with(|_| { T::regs().mcr().modify(|reg| { reg.set_hfsel(0b01); }); }); } } } macro_rules! impl_dma_methods { ($n:literal, $trait:ident) => { impl<'d, T: Instance, DMA> DacChannel<'d, T, $n, DMA> where DMA: $trait, { /// Write `data` to this channel via DMA. /// /// To prevent delays or glitches when outputing a periodic waveform, the `circular` /// flag can be set. This configures a circular DMA transfer that continually outputs /// `data`. Note that for performance reasons in circular mode the transfer-complete /// interrupt is disabled. #[cfg(not(gpdma))] pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) { // Enable DAC and DMA T::regs().cr().modify(|w| { w.set_en(Self::IDX, true); w.set_dmaen(Self::IDX, true); }); let tx_request = self.dma.request(); let dma_channel = &mut self.dma; let tx_options = crate::dma::TransferOptions { circular, half_transfer_ir: false, complete_transfer_ir: !circular, ..Default::default() }; // Initiate the correct type of DMA transfer depending on what data is passed let tx_f = match data { ValueArray::Bit8(buf) => unsafe { crate::dma::Transfer::new_write( dma_channel, tx_request, buf, T::regs().dhr8r(Self::IDX).as_ptr() as *mut u8, tx_options, ) }, ValueArray::Bit12Left(buf) => unsafe { crate::dma::Transfer::new_write( dma_channel, tx_request, buf, T::regs().dhr12l(Self::IDX).as_ptr() as *mut u16, tx_options, ) }, ValueArray::Bit12Right(buf) => unsafe { crate::dma::Transfer::new_write( dma_channel, tx_request, buf, T::regs().dhr12r(Self::IDX).as_ptr() as *mut u16, tx_options, ) }, }; tx_f.await; T::regs().cr().modify(|w| { w.set_en(Self::IDX, false); w.set_dmaen(Self::IDX, false); }); } } }; } impl_dma_methods!(1, DacDma1); impl_dma_methods!(2, DacDma2); impl<'d, T: Instance, const N: u8, DMA> Drop for DacChannel<'d, T, N, DMA> { fn drop(&mut self) { T::disable(); } } /// DAC driver. /// /// Use this struct when you want to use both channels, either together or independently. /// /// # Example /// /// ```ignore /// // Pins may need to be changed for your specific device. /// let (dac_ch1, dac_ch2) = embassy_stm32::dac::Dac::new(p.DAC, NoDma, NoDma, p.PA4, p.PA5).split(); /// ``` pub struct Dac<'d, T: Instance, DMACh1 = NoDma, DMACh2 = NoDma> { ch1: DacChannel<'d, T, 1, DMACh1>, ch2: DacChannel<'d, T, 2, DMACh2>, } impl<'d, T: Instance, DMACh1, DMACh2> Dac<'d, T, DMACh1, DMACh2> { /// Create a new `Dac` instance, consuming the underlying DAC peripheral. /// /// This struct allows you to access both channels of the DAC, where available. You can either /// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use /// the two channels together. /// /// The channels are enabled on creation and begins to drive their output pins. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will /// disable the channel; you must re-enable them with `enable()`. /// /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` /// method on the underlying channels. pub fn new( _peri: impl Peripheral

+ 'd, dma_ch1: impl Peripheral

+ 'd, dma_ch2: impl Peripheral

+ 'd, pin_ch1: impl Peripheral

+ crate::gpio::sealed::Pin> + 'd, pin_ch2: impl Peripheral

+ crate::gpio::sealed::Pin> + 'd, ) -> Self { into_ref!(dma_ch1, dma_ch2, pin_ch1, pin_ch2); pin_ch1.set_as_analog(); pin_ch2.set_as_analog(); // Enable twice to increment the DAC refcount for each channel. T::enable_and_reset(); T::enable_and_reset(); Self { ch1: DacCh1 { phantom: PhantomData, dma: dma_ch1, }, ch2: DacCh2 { phantom: PhantomData, dma: dma_ch2, }, } } /// Create a new `Dac` instance where the external output pins are not used, /// so the DAC can only be used to generate internal signals but the GPIO /// pins remain available for other functions. /// /// This struct allows you to access both channels of the DAC, where available. You can either /// call `split()` to obtain separate `DacChannel`s, or use methods on `Dac` to use the two /// channels together. /// /// The channels are set to [`Mode::NormalInternalUnbuffered`] and enabled on creation. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the /// channel; you must re-enable them with `enable()`. /// /// By default, triggering is disabled, but it can be enabled using the `set_trigger()` /// method on the underlying channels. #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))] pub fn new_internal( _peri: impl Peripheral

+ 'd, dma_ch1: impl Peripheral

+ 'd, dma_ch2: impl Peripheral

+ 'd, ) -> Self { into_ref!(dma_ch1, dma_ch2); // Enable twice to increment the DAC refcount for each channel. T::enable_and_reset(); T::enable_and_reset(); Self { ch1: DacCh1 { phantom: PhantomData, dma: dma_ch1, }, ch2: DacCh2 { phantom: PhantomData, dma: dma_ch2, }, } } /// Split this `Dac` into separate channels. /// /// You can access and move the channels around separately after splitting. pub fn split(self) -> (DacCh1<'d, T, DMACh1>, DacCh2<'d, T, DMACh2>) { (self.ch1, self.ch2) } /// Temporarily access channel 1. pub fn ch1(&mut self) -> &mut DacCh1<'d, T, DMACh1> { &mut self.ch1 } /// Temporarily access channel 2. pub fn ch2(&mut self) -> &mut DacCh2<'d, T, DMACh2> { &mut self.ch2 } /// Simultaneously update channels 1 and 2 with a new value. /// /// If triggering is not enabled, the new values are immediately output; /// otherwise, they will be output after the next trigger. pub fn set(&mut self, values: DualValue) { match values { DualValue::Bit8(v1, v2) => T::regs().dhr8rd().write(|reg| { reg.set_dhr(0, v1); reg.set_dhr(1, v2); }), DualValue::Bit12Left(v1, v2) => T::regs().dhr12ld().write(|reg| { reg.set_dhr(0, v1); reg.set_dhr(1, v2); }), DualValue::Bit12Right(v1, v2) => T::regs().dhr12rd().write(|reg| { reg.set_dhr(0, v1); reg.set_dhr(1, v2); }), } } } pub(crate) mod sealed { pub trait Instance { fn regs() -> &'static crate::pac::dac::Dac; } } pub trait Instance: sealed::Instance + RccPeripheral + 'static {} dma_trait!(DacDma1, Instance); dma_trait!(DacDma2, Instance); /// Marks a pin that can be used with the DAC pub trait DacPin: crate::gpio::Pin + 'static {} foreach_peripheral!( (dac, $inst:ident) => { // H7 uses single bit for both DAC1 and DAC2, this is a hack until a proper fix is implemented #[cfg(any(rcc_h7, rcc_h7rm0433))] impl crate::rcc::sealed::RccPeripheral for peripherals::$inst { fn frequency() -> crate::time::Hertz { critical_section::with(|_| unsafe { crate::rcc::get_freqs().pclk1 }) } fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) { // TODO: Increment refcount? crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(true)); crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false)); crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true)); } fn disable_with_cs(_cs: critical_section::CriticalSection) { // TODO: Decrement refcount? crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false)) } } #[cfg(any(rcc_h7, rcc_h7rm0433))] impl crate::rcc::RccPeripheral for peripherals::$inst {} impl crate::dac::sealed::Instance for peripherals::$inst { fn regs() -> &'static crate::pac::dac::Dac { &crate::pac::$inst } } impl crate::dac::Instance for peripherals::$inst {} }; ); macro_rules! impl_dac_pin { ($inst:ident, $pin:ident, $ch:expr) => { impl crate::dac::DacPin for crate::peripherals::$pin {} }; }