//! 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>, } /// DAC channel 1 type alias. pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>; /// DAC channel 2 type alias. 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 + '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;
}
}
/// DAC instance.
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