diff --git a/.vscode/settings.json b/.vscode/settings.json index 0e67ab82..87dd158e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ "rust-analyzer.checkOnSave.allTargets": false, "rust-analyzer.checkOnSave.command": "clippy", "rust-analyzer.cargo.noDefaultFeatures": true, + "rust-analyzer.experimental.procAttrMacros": false, "rust-analyzer.checkOnSave.noDefaultFeatures": true, "rust-analyzer.cargo.target": "thumbv7em-none-eabi", "rust-analyzer.cargo.features": [ diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 424b1c99..649b25f1 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs @@ -41,6 +41,7 @@ pub mod i2c; #[cfg(crc)] pub mod crc; +pub mod pwm; #[cfg(pwr)] pub mod pwr; #[cfg(rng)] diff --git a/embassy-stm32/src/pwm/mod.rs b/embassy-stm32/src/pwm/mod.rs new file mode 100644 index 00000000..8357b6cd --- /dev/null +++ b/embassy-stm32/src/pwm/mod.rs @@ -0,0 +1,195 @@ +use crate::gpio; +use crate::rcc::RccPeripheral; +use crate::time::Hertz; +use core::marker::PhantomData; +use embassy::util::Unborrow; +use embassy_hal_common::unborrow; +use stm32_metapac::timer::vals::Ocm; + +pub struct Pwm<'d, T: Instance> { + phantom: PhantomData<&'d mut T>, +} + +// TIM2 + +pub struct Ch1 {} +pub struct Ch2 {} +pub struct Ch3 {} +pub struct Ch4 {} + +#[derive(Clone, Copy)] +pub enum Channel { + Ch1, + Ch2, + Ch3, + Ch4, +} + +impl<'d, T: Instance> Pwm<'d, T> { + pub fn new>( + _tim: impl Unborrow + 'd, + ch1: impl Unborrow> + 'd, + ch2: impl Unborrow> + 'd, + ch3: impl Unborrow> + 'd, + ch4: impl Unborrow> + 'd, + freq: F, + ) -> Self { + unborrow!(ch1, ch2, ch3, ch4); + + T::enable(); + T::reset(); + let r = T::regs(); + + let mut this = Pwm { + phantom: PhantomData, + }; + unsafe { + ch1.configure(); + ch2.configure(); + ch3.configure(); + ch4.configure(); + } + + unsafe { + use stm32_metapac::timer::vals::Dir; + this.set_freq(freq); + r.cr1().write(|w| { + w.set_cen(true); + w.set_dir(Dir::UP) + }); + + this.set_ocm(Channel::Ch1, Ocm::PWMMODE1); + this.set_ocm(Channel::Ch2, Ocm::PWMMODE1); + this.set_ocm(Channel::Ch3, Ocm::PWMMODE1); + this.set_ocm(Channel::Ch4, Ocm::PWMMODE1); + } + this + } + + unsafe fn set_ocm(&mut self, channel: Channel, mode: Ocm) { + let r = T::regs(); + match channel { + Channel::Ch1 => r.ccmr_output(0).modify(|w| w.set_ocm(0, mode)), + Channel::Ch2 => r.ccmr_output(0).modify(|w| w.set_ocm(1, mode)), + Channel::Ch3 => r.ccmr_output(1).modify(|w| w.set_ocm(0, mode)), + Channel::Ch4 => r.ccmr_output(1).modify(|w| w.set_ocm(1, mode)), + } + } + + unsafe fn set_enable(&mut self, channel: Channel, enable: bool) { + let r = T::regs(); + match channel { + Channel::Ch1 => r.ccer().modify(|w| w.set_cce(0, enable)), + Channel::Ch2 => r.ccer().modify(|w| w.set_cce(1, enable)), + Channel::Ch3 => r.ccer().modify(|w| w.set_cce(2, enable)), + Channel::Ch4 => r.ccer().modify(|w| w.set_cce(3, enable)), + } + } + + pub fn enable(&mut self, channel: Channel) { + unsafe { self.set_enable(channel, true) } + } + + pub fn disable(&mut self, channel: Channel) { + unsafe { self.set_enable(channel, false) } + } + + pub fn set_freq>(&mut self, freq: F) { + use core::convert::TryInto; + let clk = T::frequency(); + let r = T::regs(); + let freq: Hertz = freq.into(); + let ticks: u32 = clk.0 / freq.0; + let psc: u16 = (ticks / (1 << 16)).try_into().unwrap(); + let arr: u16 = (ticks / (u32::from(psc) + 1)).try_into().unwrap(); + unsafe { + r.psc().write(|w| w.set_psc(psc)); + r.arr().write(|w| w.set_arr(arr)); + } + } + + pub fn get_max_duty(&self) -> u32 { + let r = T::regs(); + unsafe { r.arr().read().arr() as u32 } + } + + pub fn set_duty(&mut self, channel: Channel, duty: u32) { + use core::convert::TryInto; + assert!(duty < self.get_max_duty()); + let duty: u16 = duty.try_into().unwrap(); + let r = T::regs(); + unsafe { + match channel { + Channel::Ch1 => r.ccr(0).modify(|w| w.set_ccr(duty)), + Channel::Ch2 => r.ccr(1).modify(|w| w.set_ccr(duty)), + Channel::Ch3 => r.ccr(2).modify(|w| w.set_ccr(duty)), + Channel::Ch4 => r.ccr(3).modify(|w| w.set_ccr(duty)), + } + } + } +} + +pub(crate) mod sealed { + pub trait Instance { + fn regs() -> crate::pac::timer::TimGp16; + } +} + +pub trait Instance: sealed::Instance + Sized + RccPeripheral + 'static {} + +#[allow(unused)] +macro_rules! impl_timer { + ($inst:ident) => { + impl crate::pwm::sealed::Instance for crate::peripherals::$inst { + fn regs() -> crate::pac::timer::TimGp16 { + crate::pac::timer::TimGp16(crate::pac::$inst.0) + } + } + + impl crate::pwm::Instance for crate::peripherals::$inst {} + }; +} + +pub trait PwmPin: gpio::OptionalPin { + unsafe fn configure(&mut self); +} + +impl PwmPin for gpio::NoPin { + unsafe fn configure(&mut self) {} +} + +#[allow(unused)] +macro_rules! impl_pwm_pin { + ($timer:ident, $channel:ident, $pin:ident, $af:expr) => { + impl crate::pwm::PwmPin + for crate::peripherals::$pin + { + unsafe fn configure(&mut self) { + use crate::gpio::sealed::{AFType, Pin}; + use crate::gpio::Speed; + self.set_low(); + self.set_speed(Speed::VeryHigh); + self.set_as_af($af, AFType::OutputPushPull); + } + } + }; +} + +crate::pac::peripherals!( + (timer, $inst:ident) => { impl_timer!($inst); }; +); + +crate::pac::peripheral_pins!( + ($inst:ident, timer,TIM_GP16, $pin:ident, CH1, $af:expr) => { + impl_pwm_pin!($inst, Ch1, $pin, $af); + }; + ($inst:ident, timer,TIM_GP16, $pin:ident, CH2, $af:expr) => { + impl_pwm_pin!($inst, Ch2, $pin, $af); + }; + ($inst:ident, timer,TIM_GP16, $pin:ident, CH3, $af:expr) => { + impl_pwm_pin!($inst, Ch3, $pin, $af); + }; + ($inst:ident, timer,TIM_GP16, $pin:ident, CH4, $af:expr) => { + impl_pwm_pin!($inst, Ch4, $pin, $af); + }; +); diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 0f9d77f5..f4378309 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml @@ -8,7 +8,7 @@ resolver = "2" [dependencies] embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt"] } embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } -embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "time-driver-tim2", "stm32g491re", "memory-x", "unstable-pac"] } +embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "time-driver-tim3", "stm32g491re", "memory-x", "unstable-pac"] } embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } defmt = "0.3" diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs new file mode 100644 index 00000000..1aa7b85f --- /dev/null +++ b/examples/stm32g4/src/bin/pwm.rs @@ -0,0 +1,36 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +#[path = "../example_common.rs"] +mod example_common; +use embassy::executor::Spawner; +use embassy::time::{Duration, Timer}; +use embassy_stm32::gpio::NoPin; +use embassy_stm32::pwm::{Channel, Pwm}; +use embassy_stm32::time::U32Ext; +use embassy_stm32::Peripherals; +use example_common::*; + +#[embassy::main] +async fn main(_spawner: Spawner, p: Peripherals) { + info!("Hello World!"); + + let mut pwm = Pwm::new(p.TIM2, p.PA5, NoPin, NoPin, NoPin, 10000.hz()); + let max = pwm.get_max_duty(); + pwm.enable(Channel::Ch1); + + info!("PWM initialized"); + info!("PWM max duty {}", max); + + loop { + pwm.set_duty(Channel::Ch1, 0); + Timer::after(Duration::from_millis(300)).await; + pwm.set_duty(Channel::Ch1, max / 4); + Timer::after(Duration::from_millis(300)).await; + pwm.set_duty(Channel::Ch1, max / 2); + Timer::after(Duration::from_millis(300)).await; + pwm.set_duty(Channel::Ch1, max - 1); + Timer::after(Duration::from_millis(300)).await; + } +}