//! Digital to Analog Converter (DAC) #![macro_use] use core::marker::PhantomData; use crate::dma::ChannelAndRequest; use crate::mode::{Async, Blocking, Mode as PeriMode}; #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] use crate::pac::dac; use crate::rcc::{self, RccPeripheral}; use crate::{Peri, peripherals}; mod tsel; use embassy_hal_internal::PeripheralType; use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; 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]), } #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] enum ChannelEvent { Enable, Disable, } struct InnerState { channel_count: usize, } type SharedState = embassy_sync::blocking_mutex::Mutex>; struct State { state: SharedState, } impl State { /// Adjusts the channel count in response to a `ChannelEvent`, returning the updated value. pub fn adjust_channel_count(&self, event: ChannelEvent) -> usize { self.state.lock(|state| { { let mut mut_state = state.borrow_mut(); match event { ChannelEvent::Enable => { mut_state.channel_count += 1; } ChannelEvent::Disable => { mut_state.channel_count -= 1; } }; } state.borrow().channel_count }) } } /// 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, C: Channel, M: PeriMode> { phantom: PhantomData<&'d mut (T, C, M)>, #[allow(unused)] dma: Option>, } /// DAC channel 1 type alias. pub type DacCh1<'d, T, M> = DacChannel<'d, T, Ch1, M>; /// DAC channel 2 type alias. pub type DacCh2<'d, T, M> = DacChannel<'d, T, Ch2, M>; impl<'d, T: Instance, C: Channel> DacChannel<'d, T, C, Async> { /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral. /// /// The channel is enabled on creation and begin 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: Peri<'d, T>, dma: Peri<'d, impl Dma>, pin: Peri<'d, impl DacPin>) -> Self { pin.set_as_analog(); Self::new_inner( peri, new_dma!(dma), #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] Mode::NormalExternalBuffered, ) } /// 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()`. /// /// 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: Peri<'d, T>, dma: Peri<'d, impl Dma>) -> Self { Self::new_inner(peri, new_dma!(dma), Mode::NormalInternalUnbuffered) } /// 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(C::IDX, true); w.set_dmaen(C::IDX, true); }); let dma = self.dma.as_mut().unwrap(); 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 { dma.write(buf, T::regs().dhr8r(C::IDX).as_ptr() as *mut u8, tx_options) }, ValueArray::Bit12Left(buf) => unsafe { dma.write(buf, T::regs().dhr12l(C::IDX).as_ptr() as *mut u16, tx_options) }, ValueArray::Bit12Right(buf) => unsafe { dma.write(buf, T::regs().dhr12r(C::IDX).as_ptr() as *mut u16, tx_options) }, }; tx_f.await; T::regs().cr().modify(|w| { w.set_en(C::IDX, false); w.set_dmaen(C::IDX, false); }); } } impl<'d, T: Instance, C: Channel> DacChannel<'d, T, C, Blocking> { /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral. /// /// The channel is enabled on creation and begin 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_blocking(peri: Peri<'d, T>, pin: Peri<'d, impl DacPin>) -> Self { pin.set_as_analog(); Self::new_inner( peri, None, #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] Mode::NormalExternalBuffered, ) } /// 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()`. /// /// 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_blocking(peri: Peri<'d, T>) -> Self { Self::new_inner(peri, None, Mode::NormalInternalUnbuffered) } } impl<'d, T: Instance, C: Channel, M: PeriMode> DacChannel<'d, T, C, M> { fn new_inner( _peri: Peri<'d, T>, dma: Option>, #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] mode: Mode, ) -> Self { rcc::enable_and_reset::(); let mut dac = Self { phantom: PhantomData, dma, }; #[cfg(any(dac_v5, dac_v6, dac_v7))] dac.set_hfsel(); #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] dac.set_mode(mode); 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(C::IDX, on); }); }); let event = if on { ChannelEvent::Enable } else { ChannelEvent::Disable }; let channel_count = T::state().adjust_channel_count(event); // Disable the DAC only if no more channels are using it. if channel_count == 0 { rcc::disable::(); } } /// 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(C::IDX, false); reg.set_tsel(C::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(C::IDX, on); }); }); } /// Software trigger this channel. pub fn trigger(&mut self) { T::regs().swtrigr().write(|reg| { reg.set_swtrig(C::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(C::IDX, false); }); T::regs().mcr().modify(|reg| { reg.set_mode(C::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(C::IDX).write(|reg| reg.set_dhr(v)), Value::Bit12Left(v) => T::regs().dhr12l(C::IDX).write(|reg| reg.set_dhr(v)), Value::Bit12Right(v) => T::regs().dhr12r(C::IDX).write(|reg| reg.set_dhr(v)), } } /// Read the current output value of the DAC. pub fn read(&self) -> u16 { T::regs().dor(C::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); }); }); } } } impl<'d, T: Instance, C: Channel, M: PeriMode> Drop for DacChannel<'d, T, C, M> { fn drop(&mut self) { self.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_blocking(p.DAC1, p.PA4, p.PA5).split(); /// ``` pub struct Dac<'d, T: Instance, M: PeriMode> { ch1: DacChannel<'d, T, Ch1, M>, ch2: DacChannel<'d, T, Ch2, M>, } impl<'d, T: Instance> Dac<'d, T, Async> { /// 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 begin 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: Peri<'d, T>, dma_ch1: Peri<'d, impl Dma>, dma_ch2: Peri<'d, impl Dma>, pin_ch1: Peri<'d, impl DacPin + crate::gpio::Pin>, pin_ch2: Peri<'d, impl DacPin + crate::gpio::Pin>, ) -> Self { pin_ch1.set_as_analog(); pin_ch2.set_as_analog(); Self::new_inner( peri, new_dma!(dma_ch1), new_dma!(dma_ch2), #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] Mode::NormalExternalBuffered, ) } /// Create a new `Dac` instance with external output pins and unbuffered mode. /// /// This function consumes the underlying DAC peripheral and allows access to both channels. /// The channels are configured for external output with the buffer disabled. /// /// The channels are enabled on creation and begin 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. /// /// # Arguments /// /// * `peri` - The DAC peripheral instance. /// * `dma_ch1` - The DMA channel for DAC channel 1. /// * `dma_ch2` - The DMA channel for DAC channel 2. /// * `pin_ch1` - The GPIO pin for DAC channel 1 output. /// * `pin_ch2` - The GPIO pin for DAC channel 2 output. /// /// # Returns /// /// A new `Dac` instance in unbuffered mode. pub fn new_unbuffered( peri: Peri<'d, T>, dma_ch1: Peri<'d, impl Dma>, dma_ch2: Peri<'d, impl Dma>, pin_ch1: Peri<'d, impl DacPin + crate::gpio::Pin>, pin_ch2: Peri<'d, impl DacPin + crate::gpio::Pin>, ) -> Self { pin_ch1.set_as_analog(); pin_ch2.set_as_analog(); Self::new_inner( peri, new_dma!(dma_ch1), new_dma!(dma_ch2), #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] Mode::NormalExternalUnbuffered, ) } /// 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: Peri<'d, T>, dma_ch1: Peri<'d, impl Dma>, dma_ch2: Peri<'d, impl Dma>, ) -> Self { Self::new_inner( peri, new_dma!(dma_ch1), new_dma!(dma_ch2), Mode::NormalInternalUnbuffered, ) } } impl<'d, T: Instance> Dac<'d, T, Blocking> { /// 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 begin 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_blocking( peri: Peri<'d, T>, pin_ch1: Peri<'d, impl DacPin + crate::gpio::Pin>, pin_ch2: Peri<'d, impl DacPin + crate::gpio::Pin>, ) -> Self { pin_ch1.set_as_analog(); pin_ch2.set_as_analog(); Self::new_inner( peri, None, None, #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] Mode::NormalExternalBuffered, ) } /// 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: Peri<'d, T>) -> Self { Self::new_inner(peri, None, None, Mode::NormalInternalUnbuffered) } } impl<'d, T: Instance, M: PeriMode> Dac<'d, T, M> { fn new_inner( _peri: Peri<'d, T>, dma_ch1: Option>, dma_ch2: Option>, #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] mode: Mode, ) -> Self { rcc::enable_and_reset::(); let mut ch1 = DacCh1 { phantom: PhantomData, dma: dma_ch1, }; #[cfg(any(dac_v5, dac_v6, dac_v7))] ch1.set_hfsel(); #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] ch1.set_mode(mode); ch1.enable(); let mut ch2 = DacCh2 { phantom: PhantomData, dma: dma_ch2, }; #[cfg(any(dac_v5, dac_v6, dac_v7))] ch2.set_hfsel(); #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] ch2.set_mode(mode); ch2.enable(); Self { ch1, 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, M>, DacCh2<'d, T, M>) { (self.ch1, self.ch2) } /// Temporarily access channel 1. pub fn ch1(&mut self) -> &mut DacCh1<'d, T, M> { &mut self.ch1 } /// Temporarily access channel 2. pub fn ch2(&mut self) -> &mut DacCh2<'d, T, M> { &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); }), } } } trait SealedInstance { fn regs() -> crate::pac::dac::Dac; fn state() -> &'static State { static STATE: State = State { state: embassy_sync::blocking_mutex::Mutex::new(core::cell::RefCell::new(InnerState { channel_count: 0 })), }; &STATE } } /// DAC instance. #[allow(private_bounds)] pub trait Instance: SealedInstance + PeripheralType + RccPeripheral + 'static {} /// Channel 1 marker type. pub enum Ch1 {} /// Channel 2 marker type. pub enum Ch2 {} trait SealedChannel { const IDX: usize; } /// DAC channel trait. #[allow(private_bounds)] pub trait Channel: SealedChannel {} impl SealedChannel for Ch1 { const IDX: usize = 0; } impl SealedChannel for Ch2 { const IDX: usize = 1; } impl Channel for Ch1 {} impl Channel for Ch2 {} dma_trait!(Dma, Instance, Channel); pin_trait!(DacPin, Instance, Channel); foreach_peripheral!( (dac, $inst:ident) => { impl crate::dac::SealedInstance for peripherals::$inst { fn regs() -> crate::pac::dac::Dac { crate::pac::$inst } } impl crate::dac::Instance for peripherals::$inst {} }; );