diff --git a/.github/workflows/ci_dac.yml b/.github/workflows/ci_dac.yml new file mode 100644 index 00000000..945e2442 --- /dev/null +++ b/.github/workflows/ci_dac.yml @@ -0,0 +1,84 @@ +on: + push: + branches: [DAC] + pull_request: + +name: Continuous integration + +jobs: + ci: + runs-on: ubuntu-latest + strategy: + matrix: # All permutations of {rust, mcu} + rust: + - stable + mcu: + - stm32l412 + - stm32l422 + - stm32l431 + - stm32l432 + - stm32l433 + - stm32l442 + - stm32l443 + - stm32l451 + - stm32l452 + - stm32l462 + - stm32l471 + - stm32l475 + - stm32l476 + - stm32l486 + - stm32l496 + - stm32l4a6 + #- stm32l4r9 + #- stm32l4s9 + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: thumbv7em-none-eabihf + override: true + - name: build + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --verbose --release --examples --target thumbv7em-none-eabihf --features rt,unproven,${{ matrix.mcu }} + - name: test + uses: actions-rs/cargo@v1 + with: + command: test + args: --lib --target x86_64-unknown-linux-gnu --features rt,unproven,${{ matrix.mcu }} + + ci-r9: + runs-on: ubuntu-latest + strategy: + matrix: + rust: + - stable + mcu: + - stm32l4r9 + - stm32l4s9 + + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + target: thumbv7em-none-eabihf + override: true + - name: build + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --verbose --release --target thumbv7em-none-eabihf --features rt,unproven,${{ matrix.mcu }} + # note that examples were not built + - name: test + uses: actions-rs/cargo@v1 + with: + command: test + args: --lib --target x86_64-unknown-linux-gnu --features rt,unproven,${{ matrix.mcu }} diff --git a/.github/workflows/clippy_dac.yml b/.github/workflows/clippy_dac.yml new file mode 100644 index 00000000..982eb112 --- /dev/null +++ b/.github/workflows/clippy_dac.yml @@ -0,0 +1,22 @@ +on: + push: + branches: [ DAC ] + pull_request: + +name: Clippy check +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: thumbv7em-none-eabihf + override: true + components: clippy + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --examples --target thumbv7em-none-eabihf --features=stm32l432,rt,unproven diff --git a/.github/workflows/rustfmt_dac.yml b/.github/workflows/rustfmt_dac.yml new file mode 100644 index 00000000..7aa24fc3 --- /dev/null +++ b/.github/workflows/rustfmt_dac.yml @@ -0,0 +1,23 @@ +on: + push: + branches: [ DAC ] + pull_request: + +name: Code formatting check + +jobs: + fmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check diff --git a/Cargo.toml b/Cargo.toml index 769f5b15..90216cdd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -197,3 +197,9 @@ required-features = ["rt"] [[example]] name = "adc_dma" required-features = ["rt"] + +[[example]] +name = "dac" +required-features = ["rt", "stm32l476"] + + diff --git a/examples/dac.rs b/examples/dac.rs new file mode 100644 index 00000000..7ca9d21c --- /dev/null +++ b/examples/dac.rs @@ -0,0 +1,82 @@ +// #![deny(warnings)] +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +// use rtt_target::{rprintln, rtt_init_print}; + +// currently only works with these devices +// #[cfg(any(feature = "stm32l476", feature = "stm32l486", feature = "stm32l496", feature = "stm32l4a6"))] + +extern crate cortex_m; +extern crate cortex_m_rt as rt; +extern crate panic_halt; +extern crate stm32l4xx_hal as hal; + +// use hal::dac::GeneratorConfig; +use hal::delay::Delay; +use hal::hal::Direction; +use hal::prelude::*; +// use hal::rcc::Config; +use hal::stm32; +use rt::entry; + +use crate::hal::dac::DacExt; +use crate::hal::dac::DacOut; + +#[entry] +fn main() -> ! { + // rtt_init_print!(); + + 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 flash = dp.FLASH.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + let clocks = rcc.cfgr.freeze(&mut flash.acr, &mut pwr); + let mut delay = Delay::new(cp.SYST, clocks); + + let mut gpioa = dp.GPIOA.split(&mut rcc.ahb2); + let pa4 = gpioa.pa4.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + let pa5 = gpioa.pa5.into_analog(&mut gpioa.moder, &mut gpioa.pupdr); + + #[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + ))] + let (dac0, _dac1) = dp.DAC.constrain((pa4, pa5), &mut rcc.apb1r1); + + #[cfg(not(any( + // feature = "stm32l412", + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" + )))] + let dac0 = dp.DAC1.constrain(pa4, &mut rcc.apb1r1); + + let mut dac = dac0.calibrate_buffer(&mut delay).enable(); + + // let mut generator = dac1.enable_generator(GeneratorConfig::noise(11)); + + let mut dir = Direction::Upcounting; + let mut val = 0; + + loop { + // generator.trigger(); + dac.set_value(val); + match val { + 0 => dir = Direction::Upcounting, + 4095 => dir = Direction::Downcounting, + _ => (), + }; + + match dir { + Direction::Upcounting => val += 1, + Direction::Downcounting => val -= 1, + } + } +} diff --git a/src/dac.rs b/src/dac.rs new file mode 100644 index 00000000..b53a20c1 --- /dev/null +++ b/src/dac.rs @@ -0,0 +1,267 @@ +//! DAC + +use core::marker::PhantomData; +use core::mem::MaybeUninit; + +use crate::gpio::gpioa::{PA4, PA5}; +use crate::gpio::Analog; +use crate::hal::blocking::delay::DelayUs; +use crate::rcc::*; +use crate::stm32::DAC; + +pub trait DacOut { + fn set_value(&mut self, val: V); + fn get_value(&mut self) -> V; +} + +pub struct GeneratorConfig { + mode: u8, + amp: u8, +} + +impl GeneratorConfig { + pub fn triangle(amplitude: u8) -> Self { + Self { + mode: 0b10, + amp: amplitude, + } + } + + pub fn noise(seed: u8) -> Self { + Self { + mode: 0b01, + amp: seed, + } + } +} + +/// Enabled DAC (type state) +pub struct Enabled; +/// Enabled DAC without output buffer (type state) +pub struct EnabledUnbuffered; +/// Enabled DAC wave generator (type state) +pub struct WaveGenerator; +/// Disabled DAC (type state) +pub struct Disabled; + +pub trait ED {} +impl ED for Enabled {} +impl ED for EnabledUnbuffered {} +impl ED for WaveGenerator {} +impl ED for Disabled {} + +pub struct Channel1 { + _enabled: PhantomData, +} +pub struct Channel2 { + _enabled: PhantomData, +} + +/// Trait for GPIO pins that can be converted to DAC output pins +pub trait Pins { + type Output; +} + +impl Pins for PA4 { + type Output = Channel1; +} + +impl Pins for PA5 { + type Output = Channel2; +} + +impl Pins for (PA4, PA5) { + type Output = (Channel1, Channel2); +} + +// pub fn dac(_dac: DAC, _pins: PINS, rcc: &mut Rcc::APB1R1) -> PINS::Output +pub fn dac(_dac: DAC, _pins: PINS, rcc: &mut APB1R1) -> PINS::Output +where + PINS: Pins, +{ + DAC::enable(rcc); + DAC::reset(rcc); + + #[allow(clippy::uninit_assumed_init)] + unsafe { + MaybeUninit::uninit().assume_init() + } +} + +macro_rules! dac { + ($($CX:ident: ( + $en:ident, + $cen:ident, + $cal_flag:ident, + $trim:ident, + $mode:ident, + $dhrx:ident, + $dac_dor:ident, + $daccxdhr:ident, + $wave:ident, + $mamp:ident, + $ten:ident, + $swtrig:ident + ),)+) => { + $( + impl $CX { + pub fn enable(self) -> $CX { + let dac = unsafe { &(*DAC::ptr()) }; + + dac.mcr.modify(|_, w| unsafe { w.$mode().bits(1) }); + dac.cr.modify(|_, w| w.$en().set_bit()); + + $CX { + _enabled: PhantomData, + } + } + + pub fn enable_unbuffered(self) -> $CX { + let dac = unsafe { &(*DAC::ptr()) }; + + dac.mcr.modify(|_, w| unsafe { w.$mode().bits(2) }); + dac.cr.modify(|_, w| w.$en().set_bit()); + + $CX { + _enabled: PhantomData, + } + } + + pub fn enable_generator(self, config: GeneratorConfig) -> $CX { + let dac = unsafe { &(*DAC::ptr()) }; + + dac.mcr.modify(|_, w| unsafe { w.$mode().bits(1) }); + dac.cr.modify(|_, w| unsafe { + w.$wave().bits(config.mode); + w.$ten().set_bit(); + w.$mamp().bits(config.amp); + w.$en().set_bit() + }); + + $CX { + _enabled: PhantomData, + } + } + } + + impl $CX { + /// Calibrate the DAC output buffer by performing a "User + /// trimming" operation. It is useful when the VDDA/VREF+ + /// voltage or temperature differ from the factory trimming + /// conditions. + /// + /// The calibration is only valid when the DAC channel is + /// operating with the buffer enabled. If applied in other + /// modes it has no effect. + /// + /// After the calibration operation, the DAC channel is + /// disabled. + pub fn calibrate_buffer(self, delay: &mut T) -> $CX + where + T: DelayUs, + { + let dac = unsafe { &(*DAC::ptr()) }; + dac.cr.modify(|_, w| w.$en().clear_bit()); + dac.mcr.modify(|_, w| unsafe { w.$mode().bits(0) }); + dac.cr.modify(|_, w| w.$cen().set_bit()); + let mut trim = 0; + while true { + dac.ccr.modify(|_, w| unsafe { w.$trim().bits(trim) }); + delay.delay_us(64_u32); + if dac.sr.read().$cal_flag().bit() { + break; + } + trim += 1; + } + dac.cr.modify(|_, w| w.$cen().clear_bit()); + + $CX { + _enabled: PhantomData, + } + } + + /// Disable the DAC channel + pub fn disable(self) -> $CX { + let dac = unsafe { &(*DAC::ptr()) }; + dac.cr.modify(|_, w| unsafe { + w.$en().clear_bit().$wave().bits(0).$ten().clear_bit() + }); + + $CX { + _enabled: PhantomData, + } + } + } + + /// DacOut implementation available in any Enabled/Disabled + /// state + impl DacOut for $CX { + fn set_value(&mut self, val: u16) { + let dac = unsafe { &(*DAC::ptr()) }; + dac.$dhrx.write(|w| unsafe { w.bits(val as u32) }); + } + + fn get_value(&mut self) -> u16 { + let dac = unsafe { &(*DAC::ptr()) }; + dac.$dac_dor.read().bits() as u16 + } + } + + /// Wave generator state implementation + impl $CX { + pub fn trigger(&mut self) { + let dac = unsafe { &(*DAC::ptr()) }; + dac.swtrigr.write(|w| { w.$swtrig().set_bit() }); + } + } + )+ + }; +} + +pub trait DacExt { + fn constrain(self, pins: PINS, rcc: &mut APB1R1) -> PINS::Output + where + PINS: Pins; +} + +impl DacExt for DAC { + fn constrain(self, pins: PINS, rcc: &mut APB1R1) -> PINS::Output + where + PINS: Pins, + { + dac(self, pins, rcc) + } +} + +dac!( + Channel1: + ( + en1, + cen1, + cal_flag1, + otrim1, + mode1, + dhr12r1, + dor1, + dacc1dhr, + wave1, + mamp1, + ten1, + swtrig1 + ), + Channel2: + ( + en2, + cen2, + cal_flag2, + otrim2, + mode2, + dhr12r2, + dor2, + dacc2dhr, + wave2, + mamp2, + ten2, + swtrig2 + ), +); diff --git a/src/lib.rs b/src/lib.rs index 87ea2daf..56801dc3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,13 @@ pub mod adc; pub mod can; #[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] pub mod crc; +#[cfg(any( + feature = "stm32l476", + feature = "stm32l486", + feature = "stm32l496", + feature = "stm32l4a6" +))] +pub mod dac; pub mod datetime; #[cfg(not(any(feature = "stm32l4r9", feature = "stm32l4s9",)))] pub mod delay;