diff --git a/embassy-stm32/build.rs b/embassy-stm32/build.rs index 2011fd17d..7bfd290d2 100644 --- a/embassy-stm32/build.rs +++ b/embassy-stm32/build.rs @@ -996,8 +996,8 @@ fn main() { // SDMMCv1 uses the same channel for both directions, so just implement for RX (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), - (("dac", "CH1"), quote!(crate::dac::DmaCh1)), - (("dac", "CH2"), quote!(crate::dac::DmaCh2)), + (("dac", "CH1"), quote!(crate::dac::DacDma1)), + (("dac", "CH2"), quote!(crate::dac::DacDma2)), ] .into(); diff --git a/embassy-stm32/src/dac/mod.rs b/embassy-stm32/src/dac/mod.rs index 2f1010236..3caa7caab 100644 --- a/embassy-stm32/src/dac/mod.rs +++ b/embassy-stm32/src/dac/mod.rs @@ -1,10 +1,11 @@ +//! Provide access to the STM32 digital-to-analog converter (DAC). #![macro_use] -//! Provide access to the STM32 digital-to-analog converter (DAC). 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; @@ -13,6 +14,7 @@ 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))] @@ -56,32 +58,9 @@ impl Mode { #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Custom Errors -pub enum Error { - UnconfiguredChannel, - InvalidValue, -} - -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// DAC Channels -pub enum Channel { - Ch1, - Ch2, -} - -impl Channel { - const fn index(&self) -> usize { - match self { - Channel::Ch1 => 0, - Channel::Ch2 => 1, - } - } -} - -#[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 +/// 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), @@ -93,7 +72,21 @@ pub enum Value { #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] -/// Array variant of [`Value`] +/// 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]), @@ -102,400 +95,395 @@ pub enum ValueArray<'a> { // 12 bit values stored in a u16, right-aligned Bit12Right(&'a [u16]), } -/// Provide common functions for DAC channels -pub trait DacChannel { - const CHANNEL: Channel; - /// Enable trigger of the given channel - fn set_trigger_enable(&mut self, on: bool) -> Result<(), Error> { - T::regs().cr().modify(|reg| { - reg.set_ten(Self::CHANNEL.index(), on); - }); - Ok(()) - } - - /// Set mode register of the given channel - #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] - fn set_channel_mode(&mut self, mode: Mode) -> Result<(), Error> { - T::regs().mcr().modify(|reg| { - reg.set_mode(Self::CHANNEL.index(), mode.mode()); - }); - Ok(()) - } - - /// Set enable register of the given channel - fn set_channel_enable(&mut self, on: bool) -> Result<(), Error> { - T::regs().cr().modify(|reg| { - reg.set_en(Self::CHANNEL.index(), on); - }); - Ok(()) - } - - /// Enable the DAC channel `ch` - fn enable_channel(&mut self) -> Result<(), Error> { - self.set_channel_enable(true) - } - - /// Disable the DAC channel `ch` - fn disable_channel(&mut self) -> Result<(), Error> { - self.set_channel_enable(false) - } - - /// Perform a software trigger on `ch` - fn trigger(&mut self) { - T::regs().swtrigr().write(|reg| { - reg.set_swtrig(Self::CHANNEL.index(), true); - }); - } - - /// Set a value to be output by the DAC on trigger. - /// - /// The `value` is written to the corresponding "data holding register". - fn set(&mut self, value: Value) -> Result<(), Error> { - match value { - Value::Bit8(v) => T::regs().dhr8r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)), - Value::Bit12Left(v) => T::regs().dhr12l(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)), - Value::Bit12Right(v) => T::regs().dhr12r(Self::CHANNEL.index()).write(|reg| reg.set_dhr(v)), - } - Ok(()) - } -} - -/// Hold two DAC channels +/// Driver for a single DAC channel. /// -/// Note: This consumes the DAC `Instance` only once, allowing to get both channels simultaneously. -/// -/// # Example for obtaining both DAC channels -/// -/// ```ignore -/// // DMA channels and pins may need to be changed for your controller -/// let (dac_ch1, dac_ch2) = -/// embassy_stm32::dac::Dac::new(p.DAC1, p.DMA1_CH3, p.DMA1_CH4, p.PA4, p.PA5).split(); -/// ``` -pub struct Dac<'d, T: Instance, TxCh1, TxCh2> { - ch1: DacCh1<'d, T, TxCh1>, - ch2: DacCh2<'d, T, TxCh2>, -} - -/// DAC CH1 -/// -/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously. -pub struct DacCh1<'d, T: Instance, Tx> { - /// To consume T - _peri: PeripheralRef<'d, T>, - #[allow(unused)] // For chips whose DMA is not (yet) supported - dma: PeripheralRef<'d, Tx>, -} - -/// DAC CH2 -/// -/// Note: This consumes the DAC `Instance`. Use [`Dac::new`] to get both channels simultaneously. -pub struct DacCh2<'d, T: Instance, Tx> { - /// Instead of PeripheralRef to consume T +/// 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)] // For chips whose DMA is not (yet) supported - dma: PeripheralRef<'d, Tx>, + #[allow(unused)] + dma: PeripheralRef<'d, DMA>, } -impl<'d, T: Instance, Tx> DacCh1<'d, T, Tx> { - /// Obtain DAC CH1 - pub fn new( - peri: impl Peripheral

+ 'd, - dma: impl Peripheral

+ 'd, - pin: impl Peripheral

> + crate::gpio::sealed::Pin + 'd, - ) -> Self { - pin.set_as_analog(); - into_ref!(peri, dma); - T::enable_and_reset(); +pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>; +pub type DacCh2<'d, T, DMA = NoDma> = DacChannel<'d, T, 2, DMA>; - let mut dac = Self { _peri: peri, dma }; +impl<'d, T: Instance, const N: u8, DMA> DacChannel<'d, T, N, DMA> { + const IDX: usize = (N - 1) as usize; - // Configure each activated channel. All results can be `unwrap`ed since they - // will only error if the channel is not configured (i.e. ch1, ch2 are false) - #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] - dac.set_channel_mode(Mode::NormalExternalBuffered).unwrap(); - dac.enable_channel().unwrap(); - dac.set_trigger_enable(true).unwrap(); - - dac - } - - /// Select a new trigger for this channel + /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral. /// - /// **Important**: This disables the channel! - pub fn select_trigger(&mut self, trigger: TriggerSel) -> Result<(), Error> { - unwrap!(self.disable_channel()); - T::regs().cr().modify(|reg| { - reg.set_tsel(0, trigger.tsel()); - }); - Ok(()) - } - - /// Write `data` to the DAC CH1 via DMA. + /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. /// - /// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. - /// This will configure a circular DMA transfer that periodically outputs the `data`. - /// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. + /// 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()`. /// - /// **Important:** Channel 1 has to be configured for the DAC instance! - #[cfg(not(gpdma))] - pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error> - where - Tx: DmaCh1, - { - let channel = Channel::Ch1.index(); - debug!("Writing to channel {}", channel); - - // Enable DAC and DMA - T::regs().cr().modify(|w| { - w.set_en(channel, true); - w.set_dmaen(channel, 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(channel).as_ptr() as *mut u8, - tx_options, - ) - }, - ValueArray::Bit12Left(buf) => unsafe { - crate::dma::Transfer::new_write( - dma_channel, - tx_request, - buf, - T::regs().dhr12l(channel).as_ptr() as *mut u16, - tx_options, - ) - }, - ValueArray::Bit12Right(buf) => unsafe { - crate::dma::Transfer::new_write( - dma_channel, - tx_request, - buf, - T::regs().dhr12r(channel).as_ptr() as *mut u16, - tx_options, - ) - }, - }; - - tx_f.await; - - // finish dma - // TODO: Do we need to check any status registers here? - T::regs().cr().modify(|w| { - // Disable the DAC peripheral - w.set_en(channel, false); - // Disable the DMA. TODO: Is this necessary? - w.set_dmaen(channel, false); - }); - - Ok(()) - } -} - -impl<'d, T: Instance, Tx> DacCh2<'d, T, Tx> { - /// Obtain DAC CH2 + /// 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, + dma: impl Peripheral

+ 'd, + pin: impl Peripheral

+ crate::gpio::sealed::Pin> + 'd, ) -> Self { + into_ref!(dma, pin); pin.set_as_analog(); - into_ref!(_peri, dma); T::enable_and_reset(); - let mut dac = Self { phantom: PhantomData, dma, }; - - // Configure each activated channel. All results can be `unwrap`ed since they - // will only error if the channel is not configured (i.e. ch1, ch2 are false) - #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] - dac.set_channel_mode(Mode::NormalExternalBuffered).unwrap(); - dac.enable_channel().unwrap(); - dac.set_trigger_enable(true).unwrap(); - + #[cfg(any(dac_v5, dac_v6, dac_v7))] + dac.set_hfsel(); + dac.enable(); dac } - /// Select a new trigger for this channel - pub fn select_trigger(&mut self, trigger: TriggerSel) -> Result<(), Error> { - unwrap!(self.disable_channel()); - T::regs().cr().modify(|reg| { - reg.set_tsel(1, trigger.tsel()); - }); - Ok(()) - } - - /// Write `data` to the DAC CH2 via DMA. + /// 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. /// - /// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. - /// This will configure a circular DMA transfer that periodically outputs the `data`. - /// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. + /// 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()`. /// - /// **Important:** Channel 2 has to be configured for the DAC instance! - #[cfg(not(gpdma))] - pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error> - where - Tx: DmaCh2, - { - let channel = Channel::Ch2.index(); - debug!("Writing to channel {}", channel); - - // Enable DAC and DMA - T::regs().cr().modify(|w| { - w.set_en(channel, true); - w.set_dmaen(channel, 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(channel).as_ptr() as *mut u8, - tx_options, - ) - }, - ValueArray::Bit12Left(buf) => unsafe { - crate::dma::Transfer::new_write( - dma_channel, - tx_request, - buf, - T::regs().dhr12l(channel).as_ptr() as *mut u16, - tx_options, - ) - }, - ValueArray::Bit12Right(buf) => unsafe { - crate::dma::Transfer::new_write( - dma_channel, - tx_request, - buf, - T::regs().dhr12r(channel).as_ptr() as *mut u16, - tx_options, - ) - }, - }; - - tx_f.await; - - // finish dma - // TODO: Do we need to check any status registers here? - T::regs().cr().modify(|w| { - // Disable the DAC peripheral - w.set_en(channel, false); - // Disable the DMA. TODO: Is this necessary? - w.set_dmaen(channel, false); - }); - - Ok(()) - } -} - -impl<'d, T: Instance, TxCh1, TxCh2> Dac<'d, T, TxCh1, TxCh2> { - /// Create a new DAC instance with both channels. + /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument. /// - /// This is used to obtain two independent channels via `split()` for use e.g. with DMA. - 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 { - pin_ch1.set_as_analog(); - pin_ch2.set_as_analog(); - into_ref!(peri, dma_ch1, dma_ch2); + /// 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_ch1 = DacCh1 { - _peri: peri, - dma: dma_ch1, - }; - - let mut dac_ch2 = DacCh2 { + let mut dac = Self { phantom: PhantomData, - dma: dma_ch2, + dma, }; + #[cfg(any(dac_v5, dac_v6, dac_v7))] + dac.set_hfsel(); + dac.set_mode(Mode::NormalInternalUnbuffered); + dac.enable(); + dac + } - // Configure each activated channel. All results can be `unwrap`ed since they - // will only error if the channel is not configured (i.e. ch1, ch2 are false) - #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v73))] - dac_ch1.set_channel_mode(Mode::NormalExternalBuffered).unwrap(); - dac_ch1.enable_channel().unwrap(); - dac_ch1.set_trigger_enable(true).unwrap(); + /// 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); + }); + }); + } - #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] - dac_ch2.set_channel_mode(Mode::NormalExternalBuffered).unwrap(); - dac_ch2.enable_channel().unwrap(); - dac_ch2.set_trigger_enable(true).unwrap(); + /// Enable this channel. + pub fn enable(&mut self) { + self.set_enable(true) + } - Self { - ch1: dac_ch1, - ch2: dac_ch2, + /// 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)), } } - /// Split the DAC into CH1 and CH2 for independent use. - pub fn split(self) -> (DacCh1<'d, T, TxCh1>, DacCh2<'d, T, TxCh2>) { + /// 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, DacDma1); + +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) } - /// Get mutable reference to CH1 - pub fn ch1_mut(&mut self) -> &mut DacCh1<'d, T, TxCh1> { + /// Temporarily access channel 1. + pub fn ch1(&mut self) -> &mut DacCh1<'d, T, DMACh1> { &mut self.ch1 } - /// Get mutable reference to CH2 - pub fn ch2_mut(&mut self) -> &mut DacCh2<'d, T, TxCh2> { + /// Temporarily access channel 2. + pub fn ch2(&mut self) -> &mut DacCh2<'d, T, DMACh2> { &mut self.ch2 } - /// Get reference to CH1 - pub fn ch1(&mut self) -> &DacCh1<'d, T, TxCh1> { - &self.ch1 + /// 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); + }), + } } - - /// Get reference to CH2 - pub fn ch2(&mut self) -> &DacCh2<'d, T, TxCh2> { - &self.ch2 - } -} - -impl<'d, T: Instance, Tx> DacChannel for DacCh1<'d, T, Tx> { - const CHANNEL: Channel = Channel::Ch1; -} - -impl<'d, T: Instance, Tx> DacChannel for DacCh2<'d, T, Tx> { - const CHANNEL: Channel = Channel::Ch2; } pub(crate) mod sealed { @@ -505,8 +493,8 @@ pub(crate) mod sealed { } pub trait Instance: sealed::Instance + RccPeripheral + 'static {} -dma_trait!(DmaCh1, Instance); -dma_trait!(DmaCh2, Instance); +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 {} @@ -521,12 +509,14 @@ foreach_peripheral!( } 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)) } } diff --git a/examples/stm32f4/src/bin/dac.rs b/examples/stm32f4/src/bin/dac.rs index aaedcfecc..8f14d6078 100644 --- a/examples/stm32f4/src/bin/dac.rs +++ b/examples/stm32f4/src/bin/dac.rs @@ -4,7 +4,7 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dac::{DacCh1, DacChannel, Value}; +use embassy_stm32::dac::{DacCh1, Value}; use embassy_stm32::dma::NoDma; use {defmt_rtt as _, panic_probe as _}; @@ -14,11 +14,10 @@ async fn main(_spawner: Spawner) -> ! { info!("Hello World, dude!"); let mut dac = DacCh1::new(p.DAC, NoDma, p.PA4); - unwrap!(dac.set_trigger_enable(false)); loop { for v in 0..=255 { - unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); + dac.set(Value::Bit8(to_sine_wave(v))); } } } diff --git a/examples/stm32h7/src/bin/dac.rs b/examples/stm32h7/src/bin/dac.rs index 35fd6550f..f66268151 100644 --- a/examples/stm32h7/src/bin/dac.rs +++ b/examples/stm32h7/src/bin/dac.rs @@ -4,7 +4,7 @@ use cortex_m_rt::entry; use defmt::*; -use embassy_stm32::dac::{DacCh1, DacChannel, Value}; +use embassy_stm32::dac::{DacCh1, Value}; use embassy_stm32::dma::NoDma; use embassy_stm32::Config; use {defmt_rtt as _, panic_probe as _}; @@ -46,11 +46,10 @@ fn main() -> ! { let p = embassy_stm32::init(config); let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); - unwrap!(dac.set_trigger_enable(false)); loop { for v in 0..=255 { - unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); + dac.set(Value::Bit8(to_sine_wave(v))); } } } diff --git a/examples/stm32h7/src/bin/dac_dma.rs b/examples/stm32h7/src/bin/dac_dma.rs index 12783464a..c19fdd623 100644 --- a/examples/stm32h7/src/bin/dac_dma.rs +++ b/examples/stm32h7/src/bin/dac_dma.rs @@ -4,21 +4,15 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dac::{DacChannel, ValueArray}; +use embassy_stm32::dac::{DacCh1, DacCh2, ValueArray}; use embassy_stm32::pac::timer::vals::{Mms, Opm}; -use embassy_stm32::peripherals::{TIM6, TIM7}; +use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; use embassy_stm32::rcc::low_level::RccPeripheral; use embassy_stm32::time::Hertz; use embassy_stm32::timer::low_level::Basic16bitInstance; use micromath::F32Ext; use {defmt_rtt as _, panic_probe as _}; -pub type Dac1Type = - embassy_stm32::dac::DacCh1<'static, embassy_stm32::peripherals::DAC1, embassy_stm32::peripherals::DMA1_CH3>; - -pub type Dac2Type = - embassy_stm32::dac::DacCh2<'static, embassy_stm32::peripherals::DAC1, embassy_stm32::peripherals::DMA1_CH4>; - #[embassy_executor::main] async fn main(spawner: Spawner) { let mut config = embassy_stm32::Config::default(); @@ -63,7 +57,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn dac_task1(mut dac: Dac1Type) { +async fn dac_task1(mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM6 frequency is {}", TIM6::frequency()); @@ -77,8 +71,9 @@ async fn dac_task1(mut dac: Dac1Type) { error!("Reload value {} below threshold!", reload); } - dac.select_trigger(embassy_stm32::dac::TriggerSel::Tim6).unwrap(); - dac.enable_channel().unwrap(); + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim6); + dac.set_triggering(true); + dac.enable(); TIM6::enable_and_reset(); TIM6::regs().arr().modify(|w| w.set_arr(reload as u16 - 1)); @@ -100,14 +95,12 @@ async fn dac_task1(mut dac: Dac1Type) { // Loop technically not necessary if DMA circular mode is enabled loop { info!("Loop DAC1"); - if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { - error!("Could not write to dac: {}", e); - } + dac.write(ValueArray::Bit8(data), true).await; } } #[embassy_executor::task] -async fn dac_task2(mut dac: Dac2Type) { +async fn dac_task2(mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM7 frequency is {}", TIM7::frequency()); @@ -127,7 +120,9 @@ async fn dac_task2(mut dac: Dac2Type) { w.set_cen(true); }); - dac.select_trigger(embassy_stm32::dac::TriggerSel::Tim7).unwrap(); + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim7); + dac.set_triggering(true); + dac.enable(); debug!( "TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", @@ -138,9 +133,7 @@ async fn dac_task2(mut dac: Dac2Type) { data.len() ); - if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { - error!("Could not write to dac: {}", e); - } + dac.write(ValueArray::Bit8(data), true).await; } fn to_sine_wave(v: u8) -> u8 { diff --git a/examples/stm32l4/src/bin/dac.rs b/examples/stm32l4/src/bin/dac.rs index 0193a248e..d6a7ff624 100644 --- a/examples/stm32l4/src/bin/dac.rs +++ b/examples/stm32l4/src/bin/dac.rs @@ -3,7 +3,7 @@ #![feature(type_alias_impl_trait)] use defmt::*; -use embassy_stm32::dac::{DacCh1, DacChannel, Value}; +use embassy_stm32::dac::{DacCh1, Value}; use embassy_stm32::dma::NoDma; use {defmt_rtt as _, panic_probe as _}; @@ -13,11 +13,10 @@ fn main() -> ! { info!("Hello World!"); let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); - unwrap!(dac.set_trigger_enable(false)); loop { for v in 0..=255 { - unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); + dac.set(Value::Bit8(to_sine_wave(v))); } } } diff --git a/examples/stm32l4/src/bin/dac_dma.rs b/examples/stm32l4/src/bin/dac_dma.rs index c9f0a4cfe..dc86dbf43 100644 --- a/examples/stm32l4/src/bin/dac_dma.rs +++ b/examples/stm32l4/src/bin/dac_dma.rs @@ -4,21 +4,15 @@ use defmt::*; use embassy_executor::Spawner; -use embassy_stm32::dac::{DacChannel, ValueArray}; +use embassy_stm32::dac::{DacCh1, DacCh2, ValueArray}; use embassy_stm32::pac::timer::vals::{Mms, Opm}; -use embassy_stm32::peripherals::{TIM6, TIM7}; +use embassy_stm32::peripherals::{DAC1, DMA1_CH3, DMA1_CH4, TIM6, TIM7}; use embassy_stm32::rcc::low_level::RccPeripheral; use embassy_stm32::time::Hertz; use embassy_stm32::timer::low_level::Basic16bitInstance; use micromath::F32Ext; use {defmt_rtt as _, panic_probe as _}; -pub type Dac1Type = - embassy_stm32::dac::DacCh1<'static, embassy_stm32::peripherals::DAC1, embassy_stm32::peripherals::DMA1_CH3>; - -pub type Dac2Type = - embassy_stm32::dac::DacCh2<'static, embassy_stm32::peripherals::DAC1, embassy_stm32::peripherals::DMA1_CH4>; - #[embassy_executor::main] async fn main(spawner: Spawner) { let config = embassy_stm32::Config::default(); @@ -34,7 +28,7 @@ async fn main(spawner: Spawner) { } #[embassy_executor::task] -async fn dac_task1(mut dac: Dac1Type) { +async fn dac_task1(mut dac: DacCh1<'static, DAC1, DMA1_CH3>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM6 frequency is {}", TIM6::frequency()); @@ -48,8 +42,9 @@ async fn dac_task1(mut dac: Dac1Type) { error!("Reload value {} below threshold!", reload); } - dac.select_trigger(embassy_stm32::dac::TriggerSel::Tim6).unwrap(); - dac.enable_channel().unwrap(); + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim6); + dac.set_triggering(true); + dac.enable(); TIM6::enable_and_reset(); TIM6::regs().arr().modify(|w| w.set_arr(reload as u16 - 1)); @@ -71,14 +66,12 @@ async fn dac_task1(mut dac: Dac1Type) { // Loop technically not necessary if DMA circular mode is enabled loop { info!("Loop DAC1"); - if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { - error!("Could not write to dac: {}", e); - } + dac.write(ValueArray::Bit8(data), true).await; } } #[embassy_executor::task] -async fn dac_task2(mut dac: Dac2Type) { +async fn dac_task2(mut dac: DacCh2<'static, DAC1, DMA1_CH4>) { let data: &[u8; 256] = &calculate_array::<256>(); info!("TIM7 frequency is {}", TIM7::frequency()); @@ -98,7 +91,9 @@ async fn dac_task2(mut dac: Dac2Type) { w.set_cen(true); }); - dac.select_trigger(embassy_stm32::dac::TriggerSel::Tim7).unwrap(); + dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim7); + dac.set_triggering(true); + dac.enable(); debug!( "TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", @@ -109,9 +104,7 @@ async fn dac_task2(mut dac: Dac2Type) { data.len() ); - if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { - error!("Could not write to dac: {}", e); - } + dac.write(ValueArray::Bit8(data), true).await; } fn to_sine_wave(v: u8) -> u8 { diff --git a/tests/stm32/src/bin/dac.rs b/tests/stm32/src/bin/dac.rs index 10e3c3e81..824eb8803 100644 --- a/tests/stm32/src/bin/dac.rs +++ b/tests/stm32/src/bin/dac.rs @@ -10,7 +10,7 @@ use common::*; use defmt::assert; use embassy_executor::Spawner; use embassy_stm32::adc::Adc; -use embassy_stm32::dac::{DacCh1, DacChannel, Value}; +use embassy_stm32::dac::{DacCh1, Value}; use embassy_stm32::dma::NoDma; use embassy_time::{Delay, Timer}; use {defmt_rtt as _, panic_probe as _}; @@ -26,9 +26,7 @@ async fn main(_spawner: Spawner) { #[cfg(any(feature = "stm32h755zi", feature = "stm32g071rb"))] let dac_peripheral = p.DAC1; - let mut dac: DacCh1<'_, _, NoDma> = DacCh1::new(dac_peripheral, NoDma, p.PA4); - unwrap!(dac.set_trigger_enable(false)); - + let mut dac = DacCh1::new(dac_peripheral, NoDma, p.PA4); let mut adc = Adc::new(p.ADC1, &mut Delay); #[cfg(feature = "stm32h755zi")] @@ -36,7 +34,7 @@ async fn main(_spawner: Spawner) { #[cfg(any(feature = "stm32f429zi", feature = "stm32g071rb"))] let normalization_factor: i32 = 16; - unwrap!(dac.set(Value::Bit8(0))); + dac.set(Value::Bit8(0)); // Now wait a little to obtain a stable value Timer::after_millis(30).await; let offset = adc.read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4); @@ -44,7 +42,7 @@ async fn main(_spawner: Spawner) { for v in 0..=255 { // First set the DAC output value let dac_output_val = to_sine_wave(v); - unwrap!(dac.set(Value::Bit8(dac_output_val))); + dac.set(Value::Bit8(dac_output_val)); // Now wait a little to obtain a stable value Timer::after_millis(30).await;