From bdb9232bb77a4b5f77c51ad51d5ec1e8c1158f98 Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Mon, 23 Jun 2025 12:15:24 -0700 Subject: [PATCH 1/6] DAC Improvements * Add `DualDac` with simultaneous channel writes, with DMA support * Added `SampleFormat` trait and impls supporting Q1.7, Q1.11, Q1.15 signed and unsigned 8/11b types, left/right alignment * Added `DacTriggerSource` and `DacIncrementSource` traits for selecting external trigger sources with DAC instance constraints * Added DMA support to existing single channel DACs --- src/dac.rs | 89 ++++++++++++++++++- src/dac/dual.rs | 206 +++++++++++++++++++++++++++++++++++++++++++ src/dac/format.rs | 119 +++++++++++++++++++++++++ src/dac/trigger.rs | 212 +++++++++++++++++++++++++++++++++++++++++++++ src/dma/traits.rs | 2 +- 5 files changed, 626 insertions(+), 2 deletions(-) create mode 100644 src/dac/dual.rs create mode 100644 src/dac/format.rs create mode 100644 src/dac/trigger.rs diff --git a/src/dac.rs b/src/dac.rs index 874bf688..b90ff97d 100644 --- a/src/dac.rs +++ b/src/dac.rs @@ -9,12 +9,27 @@ use core::marker::PhantomData; use core::mem::MaybeUninit; use core::ops::Deref; +use crate::dac::trigger::DacTriggerSource; +use crate::dma::mux::DmaMuxResources; +use crate::dma::traits::TargetAddress; +use crate::dma::MemoryToPeripheral; use crate::gpio::{Analog, PA4, PA5, PA6}; use crate::pac; use crate::rcc::{self, *}; use crate::stm32::dac1::mcr::HFSEL; use embedded_hal::delay::DelayNs; +pub mod dual; +pub mod format; +pub mod trigger; + +/// DAC Channel identifier +#[repr(u8)] +pub enum DacChannel { + Ch1 = 0, + Ch2 = 1, +} + pub trait DacOut { fn get_value(&mut self) -> V; fn set_value(&mut self, val: V); @@ -137,6 +152,7 @@ impl SawtoothConfig { /// Enabled DAC (type state) pub struct Enabled; +pub struct EnabledDma; // / Enabled DAC without output buffer (type state) //pub struct EnabledUnbuffered; /// Enabled DAC wave generator for triangle or noise wave form (type state) @@ -282,6 +298,43 @@ impl DacCh(&mut self) + where + Source: DacTriggerSource, + { + // Set the TSELx bits to the trigger signal identifier from the DacTriggerSource impl + let dac = unsafe { &(*DAC::ptr()) }; + if CH == DacChannel::Ch1 as u8 { + unsafe { dac.cr().modify(|_, w| w.tsel1().bits(Source::SIGNAL)) }; + } + if CH == DacChannel::Ch2 as u8 { + unsafe { dac.cr().modify(|_, w| w.tsel2().bits(Source::SIGNAL)) }; + } + + // Enable the TENx flag in DAC CR + dac.cr().modify(|_, w| w.ten(CH).set_bit()); + } + + pub fn enable_dma(self, rcc: &mut Rcc) -> DacCh { + let dac = unsafe { &(*DAC::ptr()) }; + + dac.mcr() + .modify(|_, w| unsafe { w.hfsel().variant(hfsel(rcc)).mode(CH).bits(MODE_BITS) }); + + dac.cr() + .modify(|_, w| w.dmaen(CH).set_bit().en(CH).set_bit()); + + dac.dhr12r(CH as usize).write(|w| unsafe { w.bits(0) }); + + DacCh::new() + } + pub fn enable_generator( self, config: GeneratorConfig, @@ -450,7 +503,16 @@ impl } } -pub trait DacExt: Sized { +/// DMA state implementation +impl DacCh { + pub fn start(&mut self, val: u16) { + let dac = unsafe { &(*DAC::ptr()) }; + dac.dhr12r(CH as usize) + .write(|w| unsafe { w.bits(val as u32) }); + } +} + +pub trait DacExt: Instance + Sized { fn constrain(self, pins: PINS, rcc: &mut Rcc) -> PINS::Output where PINS: Pins; @@ -478,3 +540,28 @@ macro_rules! impl_dac_ext { } impl_dac_ext!(pac::DAC1, pac::DAC2, pac::DAC3, pac::DAC4,); + +macro_rules! impl_dac_dma { + ($($DAC:ty, $channel:expr, $dmamux:ident)+) => {$( + unsafe impl TargetAddress + for DacCh<$DAC, $channel, MODE_BITS, ED> + where $DAC: Instance + { + type MemSize = u32; + const REQUEST_LINE: Option = Some(DmaMuxResources::$dmamux as u8); + + fn address(&self) -> u32 { + let dac = unsafe { &(*<$DAC>::ptr()) }; + dac.dhr12r($channel as usize) as *const _ as u32 + } + } + )+}; +} + +impl_dac_dma!(pac::DAC1, 0, DAC1_CH1); +impl_dac_dma!(pac::DAC1, 1, DAC1_CH2); +impl_dac_dma!(pac::DAC2, 0, DAC2_CH1); +impl_dac_dma!(pac::DAC3, 0, DAC3_CH1); +impl_dac_dma!(pac::DAC3, 1, DAC3_CH2); +impl_dac_dma!(pac::DAC4, 0, DAC4_CH1); +impl_dac_dma!(pac::DAC4, 1, DAC4_CH2); diff --git a/src/dac/dual.rs b/src/dac/dual.rs new file mode 100644 index 00000000..61cd1a66 --- /dev/null +++ b/src/dac/dual.rs @@ -0,0 +1,206 @@ +#![deny(missing_docs)] + +//! Dual DAC +//! +//! This module provides access to the dual DAC channels of the STM32G4 series microcontrollers. +//! It allows for simultaneous access to both DAC channels and supports DMA transfers. +//! + +use core::marker::PhantomData; + +use embedded_hal::delay::DelayNs; + +use crate::dac::{ + format::SampleFormat, trigger::DacTriggerSource, DacCh, DacChannel, Disabled, Enabled, + Instance, M_EXT_PIN, +}; +use crate::dac::{hfsel, DacExt as _, Pins}; + +use crate::dac::format::{self, ToDac as _}; +use crate::dma::mux::DmaMuxResources; +use crate::dma::traits::TargetAddress; +use crate::dma::MemoryToPeripheral; +use crate::rcc::Rcc; + +/// Extension trait for [`Instance`] to create a [`DualDac`] from the given pins and RCC. +pub trait DualDacExt: Instance + Sized { + /// Create a dual DAC instance from the given pins and RCC. + /// + /// DAC calibration is performed before enabling the DAC, so + /// a DelayNs implementation is required. + /// + /// This uses the dual hold registers (DHR12xD, DHR8RD) and + /// can write to both DAC channels simultaneously with DMA support. + fn into_dual( + self, + pins: PINS, + rcc: &mut Rcc, + delay: &mut impl DelayNs, + ) -> DualDac + where + PINS: Pins< + Self, + Output = ( + DacCh, + DacCh, + ), + >; +} + +/// Dual DAC handle +pub struct DualDac { + _ch1: DacCh, + _ch2: DacCh, + _phantom: PhantomData, +} + +impl DualDac { + /// Enable DMA double mode for the specified channel. + /// This creates a single DMA request for every + /// two external hardware triggers (excluding software triggers). + #[inline(always)] + pub fn enable_dma_double(&mut self, channel: u8, enable: bool) { + let dac = unsafe { &(*DAC::ptr()) }; + if enable { + dac.mcr().modify(|_, w| w.dmadouble(channel).set_bit()); + } else { + dac.mcr().modify(|_, w| w.dmadouble(channel).clear_bit()); + } + } + + /// Enable DMA for the specified channel + #[inline(always)] + pub fn enable_dma(&mut self, channel: DacChannel, enable: bool) { + let dac = unsafe { &(*DAC::ptr()) }; + if enable { + dac.cr().modify(|_, w| w.dmaen(channel as u8).set_bit()); + } else { + dac.cr().modify(|_, w| w.dmaen(channel as u8).clear_bit()); + } + } + + /// Enable trigger for the specified channel and the [`DacTriggerSource`] + /// provided as a generic type parameter. + /// + /// This will cause the DAC to copy the hold register to the output register + /// when the trigger is raised by a timer signal or external interrupt, + /// and issue a new DMA request if DMA is enabled. + #[inline(always)] + pub fn enable_trigger(&mut self, channel: DacChannel) + where + Source: DacTriggerSource, + { + // Set the TSELx bits to the trigger signal identifier from the DacTriggerSource impl + let dac = unsafe { &(*DAC::ptr()) }; + match channel { + DacChannel::Ch1 => { + unsafe { dac.cr().modify(|_, w| w.tsel1().bits(Source::SIGNAL)) }; + } + DacChannel::Ch2 => { + unsafe { dac.cr().modify(|_, w| w.tsel2().bits(Source::SIGNAL)) }; + } + } + + // Enable the TENx flag in DAC CR + dac.cr().modify(|_, w| w.ten(channel as u8).set_bit()); + } + + /// Enable both DAC channels + #[inline(always)] + pub fn enable(self) -> DualDac { + let dac = unsafe { &(*DAC::ptr()) }; + + dac.cr().modify(|_, w| w.en(0).set_bit().en(1).set_bit()); + DualDac { + _ch1: DacCh::new(), + _ch2: DacCh::new(), + _phantom: PhantomData, + } + } +} + +impl DualDac { + /// Write to both DAC channels simultaneously. + #[inline(always)] + pub fn set_channels(&mut self, ch1: F::Scalar, ch2: F::Scalar) { + let dac = unsafe { &(*DAC::ptr()) }; + + let bits = (ch2.to_dac() as u32) << F::CH2_SHIFT | (ch1.to_dac() as u32) & F::CH1_MASK; + + match F::DEPTH { + format::SampleDepth::Bits12 => match F::ALIGNMENT { + format::Alignment::Left => { + dac.dhr12ld().write(|w| unsafe { w.bits(bits as u32) }); + } + format::Alignment::Right => { + dac.dhr12rd().write(|w| unsafe { w.bits(bits as u32) }); + } + }, + format::SampleDepth::Bits8 => { + // NOTE: 8-bit is always right aligned + dac.dhr8rd().write(|w| unsafe { w.bits(bits as u32) }); + } + } + } +} + +impl DualDacExt for DAC { + fn into_dual( + self, + pins: PINS, + rcc: &mut Rcc, + delay: &mut impl DelayNs, + ) -> DualDac + where + PINS: Pins< + Self, + Output = ( + DacCh, + DacCh, + ), + >, + { + let (ch1, ch2) = self.constrain(pins, rcc); + + let ch1 = ch1.calibrate_buffer(delay); + let ch2 = ch2.calibrate_buffer(delay); + + // Configure HFSEL + let dac = unsafe { &(*DAC::ptr()) }; + let hfsel = hfsel(rcc); + dac.mcr().modify(|_, w| w.hfsel().variant(hfsel)); + + // Apply the format configuration to both channels + for channel in 0..2 { + match F::SIGNED { + true => { + dac.mcr().modify(|_, w| w.sinformat(channel).set_bit()); + } + false => { + dac.mcr().modify(|_, w| w.sinformat(channel).clear_bit()); + } + }; + } + + DualDac { + _ch1: ch1, + _ch2: ch2, + _phantom: PhantomData, + } + } +} + +unsafe impl TargetAddress + for DualDac +{ + type MemSize = u32; + const REQUEST_LINE: Option = Some(DmaMuxResources::DAC1_CH1 as u8); + + fn address(&self) -> u32 { + let dac = unsafe { &(*DAC::ptr()) }; + match F::SIGNED { + true => dac.dhr12ld() as *const _ as u32, + false => dac.dhr12rd() as *const _ as u32, + } + } +} diff --git a/src/dac/format.rs b/src/dac/format.rs new file mode 100644 index 00000000..201e24ec --- /dev/null +++ b/src/dac/format.rs @@ -0,0 +1,119 @@ +//! STM32G4 DAC Data Sample Formats + +use fixed::types::{I1F15, I1F7}; + +/// DAC Data Alignment +pub enum Alignment { + Left, + Right, +} + +pub enum SampleDepth { + Bits12, + Bits8, +} + +pub trait SampleFormat { + /// Bit depth of the sample format (8 or 12 bits) + const DEPTH: SampleDepth; + /// Alignment of the samples in the DAC hold register. + /// Left alignment is used for signed 2's complement data + const ALIGNMENT: Alignment; + /// Signedness of the sample format + const SIGNED: bool; + /// Shift amount for Channel 2 samples into the 32 bit hold register + const CH2_SHIFT: u8; + /// Mask for Channel 1 samples in the 32 bit hold register + const CH1_MASK: u32; + + /// Scalar type for this sample format + /// Q1.7, Q1.11, Q1.15 use fixed types, unsigned are u8 or u16 + type Scalar: ToDac; +} + +/// Conversion trait for DAC data. Used to support conversion from +/// fixed point types to their bit representation as u16. +pub trait ToDac { + fn to_dac(&self) -> u16; +} + +impl ToDac for I1F7 { + fn to_dac(&self) -> u16 { + self.to_bits() as u16 + } +} + +impl ToDac for I1F15 { + fn to_dac(&self) -> u16 { + self.to_bits() as u16 + } +} + +impl ToDac for u16 { + #[inline(always)] + fn to_dac(&self) -> u16 { + *self + } +} + +impl ToDac for u8 { + #[inline(always)] + fn to_dac(&self) -> u16 { + *self as u16 + } +} + +/// Unsigned 8-bit samples +pub struct SampleU8; +impl SampleFormat for SampleU8 { + const DEPTH: SampleDepth = SampleDepth::Bits8; + const ALIGNMENT: Alignment = Alignment::Right; + const SIGNED: bool = false; + const CH2_SHIFT: u8 = 8; + const CH1_MASK: u32 = 0xFF; + type Scalar = u8; +} + +/// Unsigned 12-bit samples +pub struct SampleU12; +impl SampleFormat for SampleU12 { + const DEPTH: SampleDepth = SampleDepth::Bits12; + const ALIGNMENT: Alignment = Alignment::Right; + const SIGNED: bool = false; + const CH2_SHIFT: u8 = 16; + const CH1_MASK: u32 = 0xFFFF; + type Scalar = u16; +} + +/// Fixed point signed Q1.7 samples +pub struct SampleQ7; +impl SampleFormat for SampleQ7 { + const DEPTH: SampleDepth = SampleDepth::Bits8; + const ALIGNMENT: Alignment = Alignment::Right; + const SIGNED: bool = true; + const CH2_SHIFT: u8 = 8; + const CH1_MASK: u32 = 0xFF; + type Scalar = I1F7; +} + +/// Fixed point signed Q1.11 samples +pub struct SampleQ11; +impl SampleFormat for SampleQ11 { + const DEPTH: SampleDepth = SampleDepth::Bits12; + const ALIGNMENT: Alignment = Alignment::Left; + const SIGNED: bool = true; + const CH2_SHIFT: u8 = 16; + const CH1_MASK: u32 = 0xFFFF; + type Scalar = I1F15; +} + +/// Fixed point signed Q1.15 samples +pub struct SampleQ15; +impl SampleFormat for SampleQ15 { + const DEPTH: SampleDepth = SampleDepth::Bits12; + const ALIGNMENT: Alignment = Alignment::Left; + const SIGNED: bool = true; + const CH2_SHIFT: u8 = 16; + const CH1_MASK: u32 = 0xFFFF; + type Scalar = I1F15; +} diff --git a/src/dac/trigger.rs b/src/dac/trigger.rs new file mode 100644 index 00000000..bb1869b4 --- /dev/null +++ b/src/dac/trigger.rs @@ -0,0 +1,212 @@ +use crate::dac::Instance; +use crate::stm32::{DAC1, DAC2, DAC3, DAC4}; + +pub trait DacTriggerSource { + const SIGNAL: u8; + + #[inline(always)] + fn signal(&self) -> u8 { + Self::SIGNAL + } +} + +pub trait DacIncrementSource { + const SIGNAL: u8; + + #[inline(always)] + fn signal(&self) -> u8 { + Self::SIGNAL + } +} + +/// Implements the struct representing a DAC trigger source. +/// Sources which are valid for a DAC instance will have a [`DacTriggerSource`] +/// trait implemented on them with the const SIGNAL set to the signal +/// interconnection from RM0440 Table 187 +macro_rules! impl_dac_trigger { + ($($source:ident)+) => {$( + pub struct $source; + )+}; +} + +impl_dac_trigger!(Software); +impl_dac_trigger!(Timer1); +impl_dac_trigger!(Timer2); +impl_dac_trigger!(Timer3); +impl_dac_trigger!(Timer4); +impl_dac_trigger!(Timer5); +impl_dac_trigger!(Timer6); +impl_dac_trigger!(Timer7); +impl_dac_trigger!(Timer8); +impl_dac_trigger!(Timer15); + +impl_dac_trigger!(HrtimDacReset1); +impl_dac_trigger!(HrtimDacReset2); +impl_dac_trigger!(HrtimDacReset3); +impl_dac_trigger!(HrtimDacReset4); +impl_dac_trigger!(HrtimDacReset5); +impl_dac_trigger!(HrtimDacReset6); +impl_dac_trigger!(HrtimDacTrigger1); +impl_dac_trigger!(HrtimDacTrigger2); +impl_dac_trigger!(HrtimDacTrigger3); +impl_dac_trigger!(HrtimDacStep1); +impl_dac_trigger!(HrtimDacStep2); +impl_dac_trigger!(HrtimDacStep3); +impl_dac_trigger!(HrtimDacStep4); +impl_dac_trigger!(HrtimDacStep5); +impl_dac_trigger!(HrtimDacStep6); + +impl_dac_trigger!(Exti9); +impl_dac_trigger!(Exti10); + +/// Macro to implement the DacTriggerSource trait for each supported trigger source +/// specific to each DAC peripheral instance. +macro_rules! impl_dac_channel_trigger { + ($($DAC:ty, $source:ident, $signal:expr)+) => {$( + impl DacTriggerSource<$DAC> for $source { + const SIGNAL: u8 = $signal; + } + )+}; +} + +/// Macro to implement the DacIncrementSource trait for each supported +/// increment trigger source specific to each DAC peripheral instance. +macro_rules! impl_dac_increment_trigger { + ($($DAC:ty, $source:ident, $signal:expr)+) => {$( + impl DacIncrementSource<$DAC> for $source { + const SIGNAL: u8 = $signal; + } + )+}; +} + +impl_dac_channel_trigger!(DAC1, Software, 0); +impl_dac_channel_trigger!(DAC1, Timer8, 1); +impl_dac_channel_trigger!(DAC1, Timer7, 2); +impl_dac_channel_trigger!(DAC1, Timer15, 3); +impl_dac_channel_trigger!(DAC1, Timer2, 4); +impl_dac_channel_trigger!(DAC1, Timer4, 5); +impl_dac_channel_trigger!(DAC1, Exti9, 6); +impl_dac_channel_trigger!(DAC1, Timer6, 7); +impl_dac_channel_trigger!(DAC1, Timer3, 8); +impl_dac_channel_trigger!(DAC1, HrtimDacReset1, 9); +impl_dac_channel_trigger!(DAC1, HrtimDacReset2, 10); +impl_dac_channel_trigger!(DAC1, HrtimDacReset3, 11); +impl_dac_channel_trigger!(DAC1, HrtimDacReset4, 12); +impl_dac_channel_trigger!(DAC1, HrtimDacReset5, 13); +impl_dac_channel_trigger!(DAC1, HrtimDacReset6, 14); +impl_dac_channel_trigger!(DAC1, HrtimDacTrigger1, 15); + +impl_dac_increment_trigger!(DAC1, Software, 0); +impl_dac_increment_trigger!(DAC1, Timer8, 1); +impl_dac_increment_trigger!(DAC1, Timer7, 2); +impl_dac_increment_trigger!(DAC1, Timer15, 3); +impl_dac_increment_trigger!(DAC1, Timer2, 4); +impl_dac_increment_trigger!(DAC1, Timer4, 5); +impl_dac_increment_trigger!(DAC1, Exti10, 6); +impl_dac_increment_trigger!(DAC1, Timer6, 7); +impl_dac_increment_trigger!(DAC1, Timer3, 8); +impl_dac_increment_trigger!(DAC1, HrtimDacStep1, 9); +impl_dac_increment_trigger!(DAC1, HrtimDacStep2, 10); +impl_dac_increment_trigger!(DAC1, HrtimDacStep3, 11); +impl_dac_increment_trigger!(DAC1, HrtimDacStep4, 12); +impl_dac_increment_trigger!(DAC1, HrtimDacStep5, 13); +impl_dac_increment_trigger!(DAC1, HrtimDacStep6, 14); + +impl_dac_channel_trigger!(DAC2, Software, 0); +impl_dac_channel_trigger!(DAC2, Timer8, 1); +impl_dac_channel_trigger!(DAC2, Timer7, 2); +impl_dac_channel_trigger!(DAC2, Timer15, 3); +impl_dac_channel_trigger!(DAC2, Timer2, 4); +impl_dac_channel_trigger!(DAC2, Timer4, 5); +impl_dac_channel_trigger!(DAC2, Exti9, 6); +impl_dac_channel_trigger!(DAC2, Timer6, 7); +impl_dac_channel_trigger!(DAC2, Timer3, 8); +impl_dac_channel_trigger!(DAC2, HrtimDacReset1, 9); +impl_dac_channel_trigger!(DAC2, HrtimDacReset2, 10); +impl_dac_channel_trigger!(DAC2, HrtimDacReset3, 11); +impl_dac_channel_trigger!(DAC2, HrtimDacReset4, 12); +impl_dac_channel_trigger!(DAC2, HrtimDacReset5, 13); +impl_dac_channel_trigger!(DAC2, HrtimDacReset6, 14); +impl_dac_channel_trigger!(DAC2, HrtimDacTrigger2, 15); + +impl_dac_increment_trigger!(DAC2, Software, 0); +impl_dac_increment_trigger!(DAC2, Timer8, 1); +impl_dac_increment_trigger!(DAC2, Timer7, 2); +impl_dac_increment_trigger!(DAC2, Timer15, 3); +impl_dac_increment_trigger!(DAC2, Timer2, 4); +impl_dac_increment_trigger!(DAC2, Timer4, 5); +impl_dac_increment_trigger!(DAC2, Exti10, 6); +impl_dac_increment_trigger!(DAC2, Timer6, 7); +impl_dac_increment_trigger!(DAC2, Timer3, 8); +impl_dac_increment_trigger!(DAC2, HrtimDacStep1, 9); +impl_dac_increment_trigger!(DAC2, HrtimDacStep2, 10); +impl_dac_increment_trigger!(DAC2, HrtimDacStep3, 11); +impl_dac_increment_trigger!(DAC2, HrtimDacStep4, 12); +impl_dac_increment_trigger!(DAC2, HrtimDacStep5, 13); +impl_dac_increment_trigger!(DAC2, HrtimDacStep6, 14); + +impl_dac_channel_trigger!(DAC3, Software, 1); +impl_dac_channel_trigger!(DAC3, Timer1, 1); +impl_dac_channel_trigger!(DAC3, Timer7, 2); +impl_dac_channel_trigger!(DAC3, Timer15, 3); +impl_dac_channel_trigger!(DAC3, Timer2, 4); +impl_dac_channel_trigger!(DAC3, Timer4, 5); +impl_dac_channel_trigger!(DAC3, Exti9, 6); +impl_dac_channel_trigger!(DAC3, Timer6, 7); +impl_dac_channel_trigger!(DAC3, Timer3, 8); +impl_dac_channel_trigger!(DAC3, HrtimDacReset1, 9); +impl_dac_channel_trigger!(DAC3, HrtimDacReset2, 10); +impl_dac_channel_trigger!(DAC3, HrtimDacReset3, 11); +impl_dac_channel_trigger!(DAC3, HrtimDacReset4, 12); +impl_dac_channel_trigger!(DAC3, HrtimDacReset5, 13); +impl_dac_channel_trigger!(DAC3, HrtimDacReset6, 14); +impl_dac_channel_trigger!(DAC3, HrtimDacTrigger3, 15); + +impl_dac_increment_trigger!(DAC3, Software, 0); +impl_dac_increment_trigger!(DAC3, Timer1, 1); +impl_dac_increment_trigger!(DAC3, Timer7, 2); +impl_dac_increment_trigger!(DAC3, Timer15, 3); +impl_dac_increment_trigger!(DAC3, Timer2, 4); +impl_dac_increment_trigger!(DAC3, Timer4, 5); +impl_dac_increment_trigger!(DAC3, Exti10, 6); +impl_dac_increment_trigger!(DAC3, Timer6, 7); +impl_dac_increment_trigger!(DAC3, Timer3, 8); +impl_dac_increment_trigger!(DAC3, HrtimDacStep1, 9); +impl_dac_increment_trigger!(DAC3, HrtimDacStep2, 10); +impl_dac_increment_trigger!(DAC3, HrtimDacStep3, 11); +impl_dac_increment_trigger!(DAC3, HrtimDacStep4, 12); +impl_dac_increment_trigger!(DAC3, HrtimDacStep5, 13); +impl_dac_increment_trigger!(DAC3, HrtimDacStep6, 14); + +impl_dac_channel_trigger!(DAC4, Software, 1); +impl_dac_channel_trigger!(DAC4, Timer8, 1); +impl_dac_channel_trigger!(DAC4, Timer7, 2); +impl_dac_channel_trigger!(DAC4, Timer15, 3); +impl_dac_channel_trigger!(DAC4, Timer2, 4); +impl_dac_channel_trigger!(DAC4, Timer4, 5); +impl_dac_channel_trigger!(DAC4, Exti9, 6); +impl_dac_channel_trigger!(DAC4, Timer6, 7); +impl_dac_channel_trigger!(DAC4, Timer3, 8); +impl_dac_channel_trigger!(DAC4, HrtimDacReset1, 9); +impl_dac_channel_trigger!(DAC4, HrtimDacReset2, 10); +impl_dac_channel_trigger!(DAC4, HrtimDacReset3, 11); +impl_dac_channel_trigger!(DAC4, HrtimDacReset4, 12); +impl_dac_channel_trigger!(DAC4, HrtimDacReset5, 13); +impl_dac_channel_trigger!(DAC4, HrtimDacReset6, 14); +impl_dac_channel_trigger!(DAC4, HrtimDacTrigger1, 15); + +impl_dac_increment_trigger!(DAC4, Software, 0); +impl_dac_increment_trigger!(DAC4, Timer8, 1); +impl_dac_increment_trigger!(DAC4, Timer7, 2); +impl_dac_increment_trigger!(DAC4, Timer15, 3); +impl_dac_increment_trigger!(DAC4, Timer2, 4); +impl_dac_increment_trigger!(DAC4, Timer4, 5); +impl_dac_increment_trigger!(DAC4, Exti10, 6); +impl_dac_increment_trigger!(DAC4, Timer6, 7); +impl_dac_increment_trigger!(DAC4, Timer3, 8); +impl_dac_increment_trigger!(DAC4, HrtimDacStep1, 9); +impl_dac_increment_trigger!(DAC4, HrtimDacStep2, 10); +impl_dac_increment_trigger!(DAC4, HrtimDacStep3, 11); +impl_dac_increment_trigger!(DAC4, HrtimDacStep4, 12); +impl_dac_increment_trigger!(DAC4, HrtimDacStep5, 13); +impl_dac_increment_trigger!(DAC4, HrtimDacStep6, 14); diff --git a/src/dma/traits.rs b/src/dma/traits.rs index c72d6287..62b0544e 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -176,7 +176,7 @@ pub trait Direction { /// and for the DMA. pub unsafe trait TargetAddress { /// Memory size of the target address - type MemSize; + type MemSize: 'static; /// The address to be used by the DMA channel fn address(&self) -> u32; From 591527692b641be055e0569335b7c707ac8222dc Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Mon, 23 Jun 2025 12:32:02 -0700 Subject: [PATCH 2/6] Use fixed crate without cordic flag for DAC. Fix warnings. --- Cargo.toml | 4 ++-- src/dac/dual.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 279e89d9..3afb3034 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ stm32g4 = { version = "0.16.0", features = ["atomics"] } paste = "1.0" fugit = "0.3.7" stm32-usbd = { version = "0.7.0", optional = true } -fixed = { version = "1.28.0", optional = true } +fixed = { version = "1.28.0" } embedded-io = "0.6" [dependencies.cortex-m] @@ -106,7 +106,7 @@ defmt = [ "embedded-io/defmt-03", "embedded-test/defmt", ] -cordic = ["dep:fixed"] +cordic = [] adc3 = [] adc4 = [] adc5 = [] diff --git a/src/dac/dual.rs b/src/dac/dual.rs index 61cd1a66..a2813c80 100644 --- a/src/dac/dual.rs +++ b/src/dac/dual.rs @@ -130,15 +130,15 @@ impl DualDac { match F::DEPTH { format::SampleDepth::Bits12 => match F::ALIGNMENT { format::Alignment::Left => { - dac.dhr12ld().write(|w| unsafe { w.bits(bits as u32) }); + dac.dhr12ld().write(|w| unsafe { w.bits(bits) }); } format::Alignment::Right => { - dac.dhr12rd().write(|w| unsafe { w.bits(bits as u32) }); + dac.dhr12rd().write(|w| unsafe { w.bits(bits) }); } }, format::SampleDepth::Bits8 => { // NOTE: 8-bit is always right aligned - dac.dhr8rd().write(|w| unsafe { w.bits(bits as u32) }); + dac.dhr8rd().write(|w| unsafe { w.bits(bits) }); } } } From efbe2852d857412a1ee510cfeac6f6a7dae7ba2d Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Mon, 23 Jun 2025 12:38:15 -0700 Subject: [PATCH 3/6] Use `bit` methods in place of branches --- src/dac/dual.rs | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/src/dac/dual.rs b/src/dac/dual.rs index a2813c80..e5e283a2 100644 --- a/src/dac/dual.rs +++ b/src/dac/dual.rs @@ -61,22 +61,14 @@ impl DualDac { #[inline(always)] pub fn enable_dma_double(&mut self, channel: u8, enable: bool) { let dac = unsafe { &(*DAC::ptr()) }; - if enable { - dac.mcr().modify(|_, w| w.dmadouble(channel).set_bit()); - } else { - dac.mcr().modify(|_, w| w.dmadouble(channel).clear_bit()); - } + dac.mcr().modify(|_, w| w.dmadouble(channel).bit(enable)); } /// Enable DMA for the specified channel #[inline(always)] pub fn enable_dma(&mut self, channel: DacChannel, enable: bool) { let dac = unsafe { &(*DAC::ptr()) }; - if enable { - dac.cr().modify(|_, w| w.dmaen(channel as u8).set_bit()); - } else { - dac.cr().modify(|_, w| w.dmaen(channel as u8).clear_bit()); - } + dac.cr().modify(|_, w| w.dmaen(channel as u8).bit(enable)); } /// Enable trigger for the specified channel and the [`DacTriggerSource`] @@ -172,14 +164,7 @@ impl DualDacExt for DAC { // Apply the format configuration to both channels for channel in 0..2 { - match F::SIGNED { - true => { - dac.mcr().modify(|_, w| w.sinformat(channel).set_bit()); - } - false => { - dac.mcr().modify(|_, w| w.sinformat(channel).clear_bit()); - } - }; + dac.mcr().modify(|_, w| w.sinformat(channel).bit(F::SIGNED)); } DualDac { From 2799c650572386e4421fbc1fa87b88ffe20d99f3 Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Mon, 23 Jun 2025 12:48:20 -0700 Subject: [PATCH 4/6] Add Alignment to SampleDepth so alignment is only applicable to 12b samples. Remove ALIGNMENT associated type from SampleFormat --- src/dac/dual.rs | 2 +- src/dac/format.rs | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/src/dac/dual.rs b/src/dac/dual.rs index e5e283a2..1ae21c49 100644 --- a/src/dac/dual.rs +++ b/src/dac/dual.rs @@ -120,7 +120,7 @@ impl DualDac { let bits = (ch2.to_dac() as u32) << F::CH2_SHIFT | (ch1.to_dac() as u32) & F::CH1_MASK; match F::DEPTH { - format::SampleDepth::Bits12 => match F::ALIGNMENT { + format::SampleDepth::Bits12(alignment) => match alignment { format::Alignment::Left => { dac.dhr12ld().write(|w| unsafe { w.bits(bits) }); } diff --git a/src/dac/format.rs b/src/dac/format.rs index 201e24ec..0c8cac5b 100644 --- a/src/dac/format.rs +++ b/src/dac/format.rs @@ -2,23 +2,22 @@ use fixed::types::{I1F15, I1F7}; -/// DAC Data Alignment +/// Alignment of the samples in the DAC hold register. +/// Left alignment is used for signed 2's complement data pub enum Alignment { Left, Right, } +/// Sample bit depth and alignment pub enum SampleDepth { - Bits12, + Bits12(Alignment), Bits8, } pub trait SampleFormat { /// Bit depth of the sample format (8 or 12 bits) const DEPTH: SampleDepth; - /// Alignment of the samples in the DAC hold register. - /// Left alignment is used for signed 2's complement data - const ALIGNMENT: Alignment; /// Signedness of the sample format const SIGNED: bool; /// Shift amount for Channel 2 samples into the 32 bit hold register @@ -67,7 +66,6 @@ impl ToDac for u8 { pub struct SampleU8; impl SampleFormat for SampleU8 { const DEPTH: SampleDepth = SampleDepth::Bits8; - const ALIGNMENT: Alignment = Alignment::Right; const SIGNED: bool = false; const CH2_SHIFT: u8 = 8; const CH1_MASK: u32 = 0xFF; @@ -77,8 +75,7 @@ impl SampleFormat for SampleU8 { /// Unsigned 12-bit samples pub struct SampleU12; impl SampleFormat for SampleU12 { - const DEPTH: SampleDepth = SampleDepth::Bits12; - const ALIGNMENT: Alignment = Alignment::Right; + const DEPTH: SampleDepth = SampleDepth::Bits12(Alignment::Right); const SIGNED: bool = false; const CH2_SHIFT: u8 = 16; const CH1_MASK: u32 = 0xFFFF; @@ -89,7 +86,6 @@ impl SampleFormat for SampleU12 { pub struct SampleQ7; impl SampleFormat for SampleQ7 { const DEPTH: SampleDepth = SampleDepth::Bits8; - const ALIGNMENT: Alignment = Alignment::Right; const SIGNED: bool = true; const CH2_SHIFT: u8 = 8; const CH1_MASK: u32 = 0xFF; @@ -99,8 +95,7 @@ impl SampleFormat for SampleQ7 { /// Fixed point signed Q1.11 samples pub struct SampleQ11; impl SampleFormat for SampleQ11 { - const DEPTH: SampleDepth = SampleDepth::Bits12; - const ALIGNMENT: Alignment = Alignment::Left; + const DEPTH: SampleDepth = SampleDepth::Bits12(Alignment::Left); const SIGNED: bool = true; const CH2_SHIFT: u8 = 16; const CH1_MASK: u32 = 0xFFFF; @@ -110,8 +105,7 @@ impl SampleFormat for SampleQ11 { /// Fixed point signed Q1.15 samples pub struct SampleQ15; impl SampleFormat for SampleQ15 { - const DEPTH: SampleDepth = SampleDepth::Bits12; - const ALIGNMENT: Alignment = Alignment::Left; + const DEPTH: SampleDepth = SampleDepth::Bits12(Alignment::Left); const SIGNED: bool = true; const CH2_SHIFT: u8 = 16; const CH1_MASK: u32 = 0xFFFF; From e923fc29cf7c6373f6f0a7a60b5d583c9bf6f289 Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Mon, 23 Jun 2025 13:05:59 -0700 Subject: [PATCH 5/6] Use DacChannel in enable_dma_double --- src/dac/dual.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dac/dual.rs b/src/dac/dual.rs index 1ae21c49..6cf7df85 100644 --- a/src/dac/dual.rs +++ b/src/dac/dual.rs @@ -59,9 +59,10 @@ impl DualDac { /// This creates a single DMA request for every /// two external hardware triggers (excluding software triggers). #[inline(always)] - pub fn enable_dma_double(&mut self, channel: u8, enable: bool) { + pub fn enable_dma_double(&mut self, channel: DacChannel, enable: bool) { let dac = unsafe { &(*DAC::ptr()) }; - dac.mcr().modify(|_, w| w.dmadouble(channel).bit(enable)); + dac.mcr() + .modify(|_, w| w.dmadouble(channel as u8).bit(enable)); } /// Enable DMA for the specified channel From c3e4576ecf41e3e4f9cd29ec399535a8815ae07d Mon Sep 17 00:00:00 2001 From: boondockenergy Date: Mon, 23 Jun 2025 23:48:00 -0700 Subject: [PATCH 6/6] Add simple DualDac example This simple example configures DAC1 in DualDac mode and outputs a wrapping ramp on PA4, and its inverse on PA5 using fixed point Q1.15 format. --- examples/dual-dac.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 examples/dual-dac.rs diff --git a/examples/dual-dac.rs b/examples/dual-dac.rs new file mode 100644 index 00000000..4ce7e8d2 --- /dev/null +++ b/examples/dual-dac.rs @@ -0,0 +1,62 @@ +//! Simple Dual DAC example for STM32G4 microcontrollers. +//! +//! This simple example configures DAC1 in DualDac mode and outputs a wrapping +//! ramp on PA4, and its inverse on PA5. +//! +//! This example highlights using the [`hal::dac::format::SampleQ15`] format for +//! native Q1.15 fixed point conversion by the DAC hardware when moving from holding register +//! to output register. +//! +//! Setting a channel to 0.0 will output a DC voltage of Vref/2, negative +//! values will be in the range [0.0..Vref/2], and positive values will be [Vref/2..Vref]. +//! + +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use fixed::types::I1F15; +use hal::dac::dual::DualDacExt; +use hal::delay::SYSTDelayExt; +use hal::gpio::GpioExt; +use hal::rcc::RccExt; +use stm32g4xx_hal as hal; +mod utils; +extern crate cortex_m_rt as rt; + +use hal::stm32; +use rt::entry; + +#[entry] +fn main() -> ! { + let dp = stm32::Peripherals::take().expect("cannot take peripherals"); + let cp = cortex_m::Peripherals::take().expect("cannot take core peripherals"); + + let mut rcc = dp.RCC.constrain(); + let mut delay = cp.SYST.delay(&rcc.clocks); + + let gpioa = dp.GPIOA.split(&mut rcc); + + // Get a DualDac instance from DAC1 with channel outputs on PA4 and PA5 + let dac = dp.DAC1.into_dual::( + (gpioa.pa4, gpioa.pa5), + &mut rcc, + &mut delay, + ); + + // Enable the DAC + let mut dac = dac.enable(); + + // Create an initial value that we'll sweep using the minimum fixed point value (-1.0) + let mut value = I1F15::MIN; + + loop { + // Output value on channel 1, and inverted value on channel 2 + // This uses the DAC's dual channel hold register, and native signed format mode + // to write both channels simultaneously with a single register write operation. + dac.set_channels(value, -value); + + // Increment and wrap the value by the minimum fixed point type increment delta + value = value.wrapping_add(I1F15::DELTA); + } +}