449: STM32: Add PWM support r=Dirbaio a=bgamari

Here is a first-cut at implementing PWM support for STM32 targets via the TIM peripherals. Currently this only contains pin configuration for the STM32G0 but it would be straightforward to extend to other platforms.

Co-authored-by: Ben Gamari <ben@smart-cactus.org>
Co-authored-by: Dario Nieuwenhuis <dirbaio@dirbaio.net>
This commit is contained in:
bors[bot] 2021-11-27 02:07:43 +00:00 committed by GitHub
commit 543cc65e56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 234 additions and 1 deletions

View file

@ -6,6 +6,7 @@
"rust-analyzer.checkOnSave.allTargets": false, "rust-analyzer.checkOnSave.allTargets": false,
"rust-analyzer.checkOnSave.command": "clippy", "rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.cargo.noDefaultFeatures": true, "rust-analyzer.cargo.noDefaultFeatures": true,
"rust-analyzer.experimental.procAttrMacros": false,
"rust-analyzer.checkOnSave.noDefaultFeatures": true, "rust-analyzer.checkOnSave.noDefaultFeatures": true,
"rust-analyzer.cargo.target": "thumbv7em-none-eabi", "rust-analyzer.cargo.target": "thumbv7em-none-eabi",
"rust-analyzer.cargo.features": [ "rust-analyzer.cargo.features": [

View file

@ -41,6 +41,7 @@ pub mod i2c;
#[cfg(crc)] #[cfg(crc)]
pub mod crc; pub mod crc;
pub mod pwm;
#[cfg(pwr)] #[cfg(pwr)]
pub mod pwr; pub mod pwr;
#[cfg(rng)] #[cfg(rng)]

View file

@ -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<F: Into<Hertz>>(
_tim: impl Unborrow<Target = T> + 'd,
ch1: impl Unborrow<Target = impl PwmPin<T, Ch1>> + 'd,
ch2: impl Unborrow<Target = impl PwmPin<T, Ch2>> + 'd,
ch3: impl Unborrow<Target = impl PwmPin<T, Ch3>> + 'd,
ch4: impl Unborrow<Target = impl PwmPin<T, Ch4>> + '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<F: Into<Hertz>>(&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<Timer, Channel>: gpio::OptionalPin {
unsafe fn configure(&mut self);
}
impl<Timer, Channel> PwmPin<Timer, Channel> 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<crate::peripherals::$timer, crate::pwm::$channel>
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);
};
);

View file

@ -8,7 +8,7 @@ resolver = "2"
[dependencies] [dependencies]
embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt"] } embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt"] }
embassy-traits = { version = "0.1.0", path = "../../embassy-traits", 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" } embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" }
defmt = "0.3" defmt = "0.3"

View file

@ -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;
}
}