STM32 DAC: Rework DAC driver, support all families.

This commit is contained in:
Adam Greig 2023-11-21 03:12:36 +00:00 committed by Dario Nieuwenhuis
parent 267cbaebe6
commit 09d7950313
8 changed files with 406 additions and 435 deletions

View File

@ -996,8 +996,8 @@ fn main() {
// SDMMCv1 uses the same channel for both directions, so just implement for RX // SDMMCv1 uses the same channel for both directions, so just implement for RX
(("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)), (("sdmmc", "RX"), quote!(crate::sdmmc::SdmmcDma)),
(("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)), (("quadspi", "QUADSPI"), quote!(crate::qspi::QuadDma)),
(("dac", "CH1"), quote!(crate::dac::DmaCh1)), (("dac", "CH1"), quote!(crate::dac::DacDma1)),
(("dac", "CH2"), quote!(crate::dac::DmaCh2)), (("dac", "CH2"), quote!(crate::dac::DacDma2)),
] ]
.into(); .into();

View File

@ -1,10 +1,11 @@
//! Provide access to the STM32 digital-to-analog converter (DAC).
#![macro_use] #![macro_use]
//! Provide access to the STM32 digital-to-analog converter (DAC).
use core::marker::PhantomData; use core::marker::PhantomData;
use embassy_hal_internal::{into_ref, PeripheralRef}; use embassy_hal_internal::{into_ref, PeripheralRef};
use crate::dma::NoDma;
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
use crate::pac::dac; use crate::pac::dac;
use crate::rcc::RccPeripheral; use crate::rcc::RccPeripheral;
@ -13,6 +14,7 @@ use crate::{peripherals, Peripheral};
mod tsel; mod tsel;
pub use tsel::TriggerSel; pub use tsel::TriggerSel;
/// Operating mode for DAC channel
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] #[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))]
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
@ -56,32 +58,9 @@ impl Mode {
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Custom Errors /// Single 8 or 12 bit value that can be output by the DAC.
pub enum Error { ///
UnconfiguredChannel, /// 12-bit values outside the permitted range are silently truncated.
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
pub enum Value { pub enum Value {
// 8 bit value // 8 bit value
Bit8(u8), Bit8(u8),
@ -93,7 +72,21 @@ pub enum Value {
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))] #[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> { pub enum ValueArray<'a> {
// 8 bit values // 8 bit values
Bit8(&'a [u8]), Bit8(&'a [u8]),
@ -102,400 +95,395 @@ pub enum ValueArray<'a> {
// 12 bit values stored in a u16, right-aligned // 12 bit values stored in a u16, right-aligned
Bit12Right(&'a [u16]), Bit12Right(&'a [u16]),
} }
/// Provide common functions for DAC channels
pub trait DacChannel<T: Instance, Tx> {
const CHANNEL: Channel;
/// Enable trigger of the given channel /// Driver for a single DAC 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
/// ///
/// Note: This consumes the DAC `Instance` only once, allowing to get both channels simultaneously. /// If you want to use both channels, either together or independently,
/// /// create a [`Dac`] first and use it to access each channel.
/// # Example for obtaining both DAC channels pub struct DacChannel<'d, T: Instance, const N: u8, DMA = NoDma> {
///
/// ```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
phantom: PhantomData<&'d mut T>, phantom: PhantomData<&'d mut T>,
#[allow(unused)] // For chips whose DMA is not (yet) supported #[allow(unused)]
dma: PeripheralRef<'d, Tx>, dma: PeripheralRef<'d, DMA>,
} }
impl<'d, T: Instance, Tx> DacCh1<'d, T, Tx> { pub type DacCh1<'d, T, DMA = NoDma> = DacChannel<'d, T, 1, DMA>;
/// Obtain DAC CH1 pub type DacCh2<'d, T, DMA = NoDma> = DacChannel<'d, T, 2, DMA>;
pub fn new(
peri: impl Peripheral<P = T> + 'd,
dma: impl Peripheral<P = Tx> + 'd,
pin: impl Peripheral<P = impl DacPin<T, 1>> + crate::gpio::sealed::Pin + 'd,
) -> Self {
pin.set_as_analog();
into_ref!(peri, dma);
T::enable_and_reset();
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 /// Create a new `DacChannel` instance, consuming the underlying DAC peripheral.
// 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
/// ///
/// **Important**: This disables the channel! /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument.
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.
/// ///
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. /// The channel is enabled on creation and begins to drive the output pin.
/// This will configure a circular DMA transfer that periodically outputs the `data`. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. /// disable the channel; you must re-enable it with `enable()`.
/// ///
/// **Important:** Channel 1 has to be configured for the DAC instance! /// By default, triggering is disabled, but it can be enabled using
#[cfg(not(gpdma))] /// [`DacChannel::set_trigger()`].
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error>
where
Tx: DmaCh1<T>,
{
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
pub fn new( pub fn new(
_peri: impl Peripheral<P = T> + 'd, _peri: impl Peripheral<P = T> + 'd,
dma: impl Peripheral<P = Tx> + 'd, dma: impl Peripheral<P = DMA> + 'd,
pin: impl Peripheral<P = impl DacPin<T, 2>> + crate::gpio::sealed::Pin + 'd, pin: impl Peripheral<P = impl DacPin<T, N> + crate::gpio::sealed::Pin> + 'd,
) -> Self { ) -> Self {
into_ref!(dma, pin);
pin.set_as_analog(); pin.set_as_analog();
into_ref!(_peri, dma);
T::enable_and_reset(); T::enable_and_reset();
let mut dac = Self { let mut dac = Self {
phantom: PhantomData, phantom: PhantomData,
dma, dma,
}; };
#[cfg(any(dac_v5, dac_v6, dac_v7))]
// Configure each activated channel. All results can be `unwrap`ed since they dac.set_hfsel();
// will only error if the channel is not configured (i.e. ch1, ch2 are false) dac.enable();
#[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 dac
} }
/// Select a new trigger for this channel /// Create a new `DacChannel` instance where the external output pin is not used,
pub fn select_trigger(&mut self, trigger: TriggerSel) -> Result<(), Error> { /// so the DAC can only be used to generate internal signals.
unwrap!(self.disable_channel()); /// The GPIO pin is therefore available to be used for other functions.
T::regs().cr().modify(|reg| {
reg.set_tsel(1, trigger.tsel());
});
Ok(())
}
/// Write `data` to the DAC CH2 via DMA.
/// ///
/// To prevent delays/glitches when outputting a periodic waveform, the `circular` flag can be set. /// The channel is set to [`Mode::NormalInternalUnbuffered`] and enabled on creation.
/// This will configure a circular DMA transfer that periodically outputs the `data`. /// Note that some methods, such as `set_trigger()` and `set_mode()`, will disable the
/// Note that for performance reasons in circular mode the transfer complete interrupt is disabled. /// channel; you must re-enable it with `enable()`.
/// ///
/// **Important:** Channel 2 has to be configured for the DAC instance! /// If you're not using DMA, pass [`dma::NoDma`] for the `dma` argument.
#[cfg(not(gpdma))]
pub async fn write(&mut self, data: ValueArray<'_>, circular: bool) -> Result<(), Error>
where
Tx: DmaCh2<T>,
{
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.
/// ///
/// This is used to obtain two independent channels via `split()` for use e.g. with DMA. /// By default, triggering is disabled, but it can be enabled using
pub fn new( /// [`DacChannel::set_trigger()`].
peri: impl Peripheral<P = T> + 'd, #[cfg(all(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7), not(any(stm32h56x, stm32h57x))))]
dma_ch1: impl Peripheral<P = TxCh1> + 'd, pub fn new_internal(_peri: impl Peripheral<P = T> + 'd, dma: impl Peripheral<P = DMA> + 'd) -> Self {
dma_ch2: impl Peripheral<P = TxCh2> + 'd, into_ref!(dma);
pin_ch1: impl Peripheral<P = impl DacPin<T, 1>> + crate::gpio::sealed::Pin + 'd,
pin_ch2: impl Peripheral<P = impl DacPin<T, 2>> + crate::gpio::sealed::Pin + 'd,
) -> Self {
pin_ch1.set_as_analog();
pin_ch2.set_as_analog();
into_ref!(peri, dma_ch1, dma_ch2);
T::enable_and_reset(); T::enable_and_reset();
let mut dac = Self {
let mut dac_ch1 = DacCh1 {
_peri: peri,
dma: dma_ch1,
};
let mut dac_ch2 = DacCh2 {
phantom: PhantomData, 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 /// Enable or disable this channel.
// will only error if the channel is not configured (i.e. ch1, ch2 are false) pub fn set_enable(&mut self, on: bool) {
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v73))] critical_section::with(|_| {
dac_ch1.set_channel_mode(Mode::NormalExternalBuffered).unwrap(); T::regs().cr().modify(|reg| {
dac_ch1.enable_channel().unwrap(); reg.set_en(Self::IDX, on);
dac_ch1.set_trigger_enable(true).unwrap(); });
});
}
#[cfg(any(dac_v3, dac_v4, dac_v5, dac_v6, dac_v7))] /// Enable this channel.
dac_ch2.set_channel_mode(Mode::NormalExternalBuffered).unwrap(); pub fn enable(&mut self) {
dac_ch2.enable_channel().unwrap(); self.set_enable(true)
dac_ch2.set_trigger_enable(true).unwrap(); }
Self { /// Disable this channel.
ch1: dac_ch1, pub fn disable(&mut self) {
ch2: dac_ch2, 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. /// Read the current output value of the DAC.
pub fn split(self) -> (DacCh1<'d, T, TxCh1>, DacCh2<'d, T, TxCh2>) { 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<T>,
{
/// 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<P = T> + 'd,
dma_ch1: impl Peripheral<P = DMACh1> + 'd,
dma_ch2: impl Peripheral<P = DMACh2> + 'd,
pin_ch1: impl Peripheral<P = impl DacPin<T, 1> + crate::gpio::sealed::Pin> + 'd,
pin_ch2: impl Peripheral<P = impl DacPin<T, 2> + 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<P = T> + 'd,
dma_ch1: impl Peripheral<P = DMACh1> + 'd,
dma_ch2: impl Peripheral<P = DMACh2> + '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) (self.ch1, self.ch2)
} }
/// Get mutable reference to CH1 /// Temporarily access channel 1.
pub fn ch1_mut(&mut self) -> &mut DacCh1<'d, T, TxCh1> { pub fn ch1(&mut self) -> &mut DacCh1<'d, T, DMACh1> {
&mut self.ch1 &mut self.ch1
} }
/// Get mutable reference to CH2 /// Temporarily access channel 2.
pub fn ch2_mut(&mut self) -> &mut DacCh2<'d, T, TxCh2> { pub fn ch2(&mut self) -> &mut DacCh2<'d, T, DMACh2> {
&mut self.ch2 &mut self.ch2
} }
/// Get reference to CH1 /// Simultaneously update channels 1 and 2 with a new value.
pub fn ch1(&mut self) -> &DacCh1<'d, T, TxCh1> { ///
&self.ch1 /// 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<T, Tx> for DacCh1<'d, T, Tx> {
const CHANNEL: Channel = Channel::Ch1;
}
impl<'d, T: Instance, Tx> DacChannel<T, Tx> for DacCh2<'d, T, Tx> {
const CHANNEL: Channel = Channel::Ch2;
} }
pub(crate) mod sealed { pub(crate) mod sealed {
@ -505,8 +493,8 @@ pub(crate) mod sealed {
} }
pub trait Instance: sealed::Instance + RccPeripheral + 'static {} pub trait Instance: sealed::Instance + RccPeripheral + 'static {}
dma_trait!(DmaCh1, Instance); dma_trait!(DacDma1, Instance);
dma_trait!(DmaCh2, Instance); dma_trait!(DacDma2, Instance);
/// Marks a pin that can be used with the DAC /// Marks a pin that can be used with the DAC
pub trait DacPin<T: Instance, const C: u8>: crate::gpio::Pin + 'static {} pub trait DacPin<T: Instance, const C: u8>: crate::gpio::Pin + 'static {}
@ -521,12 +509,14 @@ foreach_peripheral!(
} }
fn enable_and_reset_with_cs(_cs: critical_section::CriticalSection) { 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(true));
crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false)); crate::pac::RCC.apb1lrstr().modify(|w| w.set_dac12rst(false));
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true)); crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(true));
} }
fn disable_with_cs(_cs: critical_section::CriticalSection) { fn disable_with_cs(_cs: critical_section::CriticalSection) {
// TODO: Decrement refcount?
crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false)) crate::pac::RCC.apb1lenr().modify(|w| w.set_dac12en(false))
} }
} }

View File

@ -4,7 +4,7 @@
use defmt::*; use defmt::*;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::dac::{DacCh1, DacChannel, Value}; use embassy_stm32::dac::{DacCh1, Value};
use embassy_stm32::dma::NoDma; use embassy_stm32::dma::NoDma;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
@ -14,11 +14,10 @@ async fn main(_spawner: Spawner) -> ! {
info!("Hello World, dude!"); info!("Hello World, dude!");
let mut dac = DacCh1::new(p.DAC, NoDma, p.PA4); let mut dac = DacCh1::new(p.DAC, NoDma, p.PA4);
unwrap!(dac.set_trigger_enable(false));
loop { loop {
for v in 0..=255 { for v in 0..=255 {
unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); dac.set(Value::Bit8(to_sine_wave(v)));
} }
} }
} }

View File

@ -4,7 +4,7 @@
use cortex_m_rt::entry; use cortex_m_rt::entry;
use defmt::*; use defmt::*;
use embassy_stm32::dac::{DacCh1, DacChannel, Value}; use embassy_stm32::dac::{DacCh1, Value};
use embassy_stm32::dma::NoDma; use embassy_stm32::dma::NoDma;
use embassy_stm32::Config; use embassy_stm32::Config;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
@ -46,11 +46,10 @@ fn main() -> ! {
let p = embassy_stm32::init(config); let p = embassy_stm32::init(config);
let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4);
unwrap!(dac.set_trigger_enable(false));
loop { loop {
for v in 0..=255 { for v in 0..=255 {
unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); dac.set(Value::Bit8(to_sine_wave(v)));
} }
} }
} }

View File

@ -4,21 +4,15 @@
use defmt::*; use defmt::*;
use embassy_executor::Spawner; 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::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::rcc::low_level::RccPeripheral;
use embassy_stm32::time::Hertz; use embassy_stm32::time::Hertz;
use embassy_stm32::timer::low_level::Basic16bitInstance; use embassy_stm32::timer::low_level::Basic16bitInstance;
use micromath::F32Ext; use micromath::F32Ext;
use {defmt_rtt as _, panic_probe as _}; 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] #[embassy_executor::main]
async fn main(spawner: Spawner) { async fn main(spawner: Spawner) {
let mut config = embassy_stm32::Config::default(); let mut config = embassy_stm32::Config::default();
@ -63,7 +57,7 @@ async fn main(spawner: Spawner) {
} }
#[embassy_executor::task] #[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>(); let data: &[u8; 256] = &calculate_array::<256>();
info!("TIM6 frequency is {}", TIM6::frequency()); info!("TIM6 frequency is {}", TIM6::frequency());
@ -77,8 +71,9 @@ async fn dac_task1(mut dac: Dac1Type) {
error!("Reload value {} below threshold!", reload); error!("Reload value {} below threshold!", reload);
} }
dac.select_trigger(embassy_stm32::dac::TriggerSel::Tim6).unwrap(); dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim6);
dac.enable_channel().unwrap(); dac.set_triggering(true);
dac.enable();
TIM6::enable_and_reset(); TIM6::enable_and_reset();
TIM6::regs().arr().modify(|w| w.set_arr(reload as u16 - 1)); 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 technically not necessary if DMA circular mode is enabled
loop { loop {
info!("Loop DAC1"); info!("Loop DAC1");
if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { dac.write(ValueArray::Bit8(data), true).await;
error!("Could not write to dac: {}", e);
}
} }
} }
#[embassy_executor::task] #[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>(); let data: &[u8; 256] = &calculate_array::<256>();
info!("TIM7 frequency is {}", TIM7::frequency()); info!("TIM7 frequency is {}", TIM7::frequency());
@ -127,7 +120,9 @@ async fn dac_task2(mut dac: Dac2Type) {
w.set_cen(true); 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!( debug!(
"TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", "TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}",
@ -138,9 +133,7 @@ async fn dac_task2(mut dac: Dac2Type) {
data.len() data.len()
); );
if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { dac.write(ValueArray::Bit8(data), true).await;
error!("Could not write to dac: {}", e);
}
} }
fn to_sine_wave(v: u8) -> u8 { fn to_sine_wave(v: u8) -> u8 {

View File

@ -3,7 +3,7 @@
#![feature(type_alias_impl_trait)] #![feature(type_alias_impl_trait)]
use defmt::*; use defmt::*;
use embassy_stm32::dac::{DacCh1, DacChannel, Value}; use embassy_stm32::dac::{DacCh1, Value};
use embassy_stm32::dma::NoDma; use embassy_stm32::dma::NoDma;
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
@ -13,11 +13,10 @@ fn main() -> ! {
info!("Hello World!"); info!("Hello World!");
let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4); let mut dac = DacCh1::new(p.DAC1, NoDma, p.PA4);
unwrap!(dac.set_trigger_enable(false));
loop { loop {
for v in 0..=255 { for v in 0..=255 {
unwrap!(dac.set(Value::Bit8(to_sine_wave(v)))); dac.set(Value::Bit8(to_sine_wave(v)));
} }
} }
} }

View File

@ -4,21 +4,15 @@
use defmt::*; use defmt::*;
use embassy_executor::Spawner; 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::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::rcc::low_level::RccPeripheral;
use embassy_stm32::time::Hertz; use embassy_stm32::time::Hertz;
use embassy_stm32::timer::low_level::Basic16bitInstance; use embassy_stm32::timer::low_level::Basic16bitInstance;
use micromath::F32Ext; use micromath::F32Ext;
use {defmt_rtt as _, panic_probe as _}; 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] #[embassy_executor::main]
async fn main(spawner: Spawner) { async fn main(spawner: Spawner) {
let config = embassy_stm32::Config::default(); let config = embassy_stm32::Config::default();
@ -34,7 +28,7 @@ async fn main(spawner: Spawner) {
} }
#[embassy_executor::task] #[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>(); let data: &[u8; 256] = &calculate_array::<256>();
info!("TIM6 frequency is {}", TIM6::frequency()); info!("TIM6 frequency is {}", TIM6::frequency());
@ -48,8 +42,9 @@ async fn dac_task1(mut dac: Dac1Type) {
error!("Reload value {} below threshold!", reload); error!("Reload value {} below threshold!", reload);
} }
dac.select_trigger(embassy_stm32::dac::TriggerSel::Tim6).unwrap(); dac.set_trigger(embassy_stm32::dac::TriggerSel::Tim6);
dac.enable_channel().unwrap(); dac.set_triggering(true);
dac.enable();
TIM6::enable_and_reset(); TIM6::enable_and_reset();
TIM6::regs().arr().modify(|w| w.set_arr(reload as u16 - 1)); 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 technically not necessary if DMA circular mode is enabled
loop { loop {
info!("Loop DAC1"); info!("Loop DAC1");
if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { dac.write(ValueArray::Bit8(data), true).await;
error!("Could not write to dac: {}", e);
}
} }
} }
#[embassy_executor::task] #[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>(); let data: &[u8; 256] = &calculate_array::<256>();
info!("TIM7 frequency is {}", TIM7::frequency()); info!("TIM7 frequency is {}", TIM7::frequency());
@ -98,7 +91,9 @@ async fn dac_task2(mut dac: Dac2Type) {
w.set_cen(true); 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!( debug!(
"TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}", "TIM7 Frequency {}, Target Frequency {}, Reload {}, Reload as u16 {}, Samples {}",
@ -109,9 +104,7 @@ async fn dac_task2(mut dac: Dac2Type) {
data.len() data.len()
); );
if let Err(e) = dac.write(ValueArray::Bit8(data), true).await { dac.write(ValueArray::Bit8(data), true).await;
error!("Could not write to dac: {}", e);
}
} }
fn to_sine_wave(v: u8) -> u8 { fn to_sine_wave(v: u8) -> u8 {

View File

@ -10,7 +10,7 @@ use common::*;
use defmt::assert; use defmt::assert;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_stm32::adc::Adc; 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_stm32::dma::NoDma;
use embassy_time::{Delay, Timer}; use embassy_time::{Delay, Timer};
use {defmt_rtt as _, panic_probe as _}; use {defmt_rtt as _, panic_probe as _};
@ -26,9 +26,7 @@ async fn main(_spawner: Spawner) {
#[cfg(any(feature = "stm32h755zi", feature = "stm32g071rb"))] #[cfg(any(feature = "stm32h755zi", feature = "stm32g071rb"))]
let dac_peripheral = p.DAC1; let dac_peripheral = p.DAC1;
let mut dac: DacCh1<'_, _, NoDma> = DacCh1::new(dac_peripheral, NoDma, p.PA4); let mut dac = DacCh1::new(dac_peripheral, NoDma, p.PA4);
unwrap!(dac.set_trigger_enable(false));
let mut adc = Adc::new(p.ADC1, &mut Delay); let mut adc = Adc::new(p.ADC1, &mut Delay);
#[cfg(feature = "stm32h755zi")] #[cfg(feature = "stm32h755zi")]
@ -36,7 +34,7 @@ async fn main(_spawner: Spawner) {
#[cfg(any(feature = "stm32f429zi", feature = "stm32g071rb"))] #[cfg(any(feature = "stm32f429zi", feature = "stm32g071rb"))]
let normalization_factor: i32 = 16; 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 // Now wait a little to obtain a stable value
Timer::after_millis(30).await; Timer::after_millis(30).await;
let offset = adc.read(&mut unsafe { embassy_stm32::Peripherals::steal() }.PA4); 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 { for v in 0..=255 {
// First set the DAC output value // First set the DAC output value
let dac_output_val = to_sine_wave(v); 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 // Now wait a little to obtain a stable value
Timer::after_millis(30).await; Timer::after_millis(30).await;