Merge #449
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:
commit
543cc65e56
5 changed files with 234 additions and 1 deletions
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -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": [
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
195
embassy-stm32/src/pwm/mod.rs
Normal file
195
embassy-stm32/src/pwm/mod.rs
Normal 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);
|
||||||
|
};
|
||||||
|
);
|
|
@ -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"
|
||||||
|
|
36
examples/stm32g4/src/bin/pwm.rs
Normal file
36
examples/stm32g4/src/bin/pwm.rs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue