Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -106,7 +106,7 @@ defmt = [
"embedded-io/defmt-03",
"embedded-test/defmt",
]
cordic = ["dep:fixed"]
cordic = []
adc3 = []
adc4 = []
adc5 = []
Expand Down
89 changes: 88 additions & 1 deletion src/dac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<V> {
fn get_value(&mut self) -> V;
fn set_value(&mut self, val: V);
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -282,6 +298,43 @@ impl<DAC: Instance, const CH: u8, const MODE_BITS: u8> DacCh<DAC, CH, MODE_BITS,
DacCh::new()
}

/// Enable trigger for the channel using the trigger source 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<Source>(&mut self)
where
Source: DacTriggerSource<DAC>,
{
// 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<DAC, CH, MODE_BITS, EnabledDma> {
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,
Expand Down Expand Up @@ -450,7 +503,16 @@ impl<DAC: Instance, const CH: u8, const MODE_BITS: u8>
}
}

pub trait DacExt: Sized {
/// DMA state implementation
impl<DAC: Instance, const CH: u8, const MODE_BITS: u8> DacCh<DAC, CH, MODE_BITS, EnabledDma> {
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<PINS>(self, pins: PINS, rcc: &mut Rcc) -> PINS::Output
where
PINS: Pins<Self>;
Expand Down Expand Up @@ -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<const MODE_BITS: u8, ED> TargetAddress<MemoryToPeripheral>
for DacCh<$DAC, $channel, MODE_BITS, ED>
where $DAC: Instance
{
type MemSize = u32;
const REQUEST_LINE: Option<u8> = 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);
191 changes: 191 additions & 0 deletions src/dac/dual.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
#![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<F: SampleFormat, PINS>(
self,
pins: PINS,
rcc: &mut Rcc,
delay: &mut impl DelayNs,
) -> DualDac<Self, F, Disabled>
where
PINS: Pins<
Self,
Output = (
DacCh<Self, 0, M_EXT_PIN, Disabled>,
DacCh<Self, 1, M_EXT_PIN, Disabled>,
),
>;
}

/// Dual DAC handle
pub struct DualDac<DAC: Instance, F: SampleFormat, State> {
_ch1: DacCh<DAC, 0, M_EXT_PIN, State>,
_ch2: DacCh<DAC, 1, M_EXT_PIN, State>,
_phantom: PhantomData<F>,
}

impl<DAC: Instance, F: SampleFormat> DualDac<DAC, F, Disabled> {
/// 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()) };
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does DacCh::enable_dma consume self and turn it into a new type while this one does not?

Also, is it ok to have set_channels still available after dma has been enabled?

let dac = unsafe { &(*DAC::ptr()) };
dac.cr().modify(|_, w| w.dmaen(channel as u8).bit(enable));
}

/// 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<Source>(&mut self, channel: DacChannel)
where
Source: DacTriggerSource<DAC>,
{
// 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<DAC, F, Enabled> {
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<DAC: Instance, F: SampleFormat> DualDac<DAC, F, Enabled> {
/// 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(alignment) => match alignment {
format::Alignment::Left => {
dac.dhr12ld().write(|w| unsafe { w.bits(bits) });
}
format::Alignment::Right => {
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) });
}
}
}
}

impl<DAC: Instance> DualDacExt for DAC {
fn into_dual<F: SampleFormat, PINS>(
self,
pins: PINS,
rcc: &mut Rcc,
delay: &mut impl DelayNs,
) -> DualDac<DAC, F, Disabled>
where
PINS: Pins<
Self,
Output = (
DacCh<Self, 0, M_EXT_PIN, Disabled>,
DacCh<Self, 1, M_EXT_PIN, Disabled>,
),
>,
{
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 {
dac.mcr().modify(|_, w| w.sinformat(channel).bit(F::SIGNED));
}

DualDac {
_ch1: ch1,
_ch2: ch2,
_phantom: PhantomData,
}
}
}

unsafe impl<DAC: Instance, F: SampleFormat, State> TargetAddress<MemoryToPeripheral>
for DualDac<DAC, F, State>
{
type MemSize = u32;
const REQUEST_LINE: Option<u8> = 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,
}
}
}
Loading
Loading