diff options
| author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-11-27 02:07:43 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2021-11-27 02:07:43 +0000 |
| commit | 543cc65e569d24d07c90e97752b4cfc995d33dc0 (patch) | |
| tree | 34615657d5507b9d043f86cb48048e22cb69c432 | |
| parent | 793f4b1f7d6d1452d665fc7ec70bddd3ac7a69b9 (diff) | |
| parent | 006e567716362acfde90afcfd8a25be1fe0d6897 (diff) | |
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 <[email protected]>
Co-authored-by: Dario Nieuwenhuis <[email protected]>
| -rw-r--r-- | .vscode/settings.json | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/lib.rs | 1 | ||||
| -rw-r--r-- | embassy-stm32/src/pwm/mod.rs | 195 | ||||
| -rw-r--r-- | examples/stm32g4/Cargo.toml | 2 | ||||
| -rw-r--r-- | examples/stm32g4/src/bin/pwm.rs | 36 |
5 files changed, 234 insertions, 1 deletions
diff --git a/.vscode/settings.json b/.vscode/settings.json index 0e67ab824..87dd158ec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | "rust-analyzer.checkOnSave.allTargets": false, | 6 | "rust-analyzer.checkOnSave.allTargets": false, |
| 7 | "rust-analyzer.checkOnSave.command": "clippy", | 7 | "rust-analyzer.checkOnSave.command": "clippy", |
| 8 | "rust-analyzer.cargo.noDefaultFeatures": true, | 8 | "rust-analyzer.cargo.noDefaultFeatures": true, |
| 9 | "rust-analyzer.experimental.procAttrMacros": false, | ||
| 9 | "rust-analyzer.checkOnSave.noDefaultFeatures": true, | 10 | "rust-analyzer.checkOnSave.noDefaultFeatures": true, |
| 10 | "rust-analyzer.cargo.target": "thumbv7em-none-eabi", | 11 | "rust-analyzer.cargo.target": "thumbv7em-none-eabi", |
| 11 | "rust-analyzer.cargo.features": [ | 12 | "rust-analyzer.cargo.features": [ |
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs index 424b1c994..649b25f10 100644 --- a/embassy-stm32/src/lib.rs +++ b/embassy-stm32/src/lib.rs | |||
| @@ -41,6 +41,7 @@ pub mod i2c; | |||
| 41 | 41 | ||
| 42 | #[cfg(crc)] | 42 | #[cfg(crc)] |
| 43 | pub mod crc; | 43 | pub mod crc; |
| 44 | pub mod pwm; | ||
| 44 | #[cfg(pwr)] | 45 | #[cfg(pwr)] |
| 45 | pub mod pwr; | 46 | pub mod pwr; |
| 46 | #[cfg(rng)] | 47 | #[cfg(rng)] |
diff --git a/embassy-stm32/src/pwm/mod.rs b/embassy-stm32/src/pwm/mod.rs new file mode 100644 index 000000000..8357b6cdc --- /dev/null +++ b/embassy-stm32/src/pwm/mod.rs | |||
| @@ -0,0 +1,195 @@ | |||
| 1 | use crate::gpio; | ||
| 2 | use crate::rcc::RccPeripheral; | ||
| 3 | use crate::time::Hertz; | ||
| 4 | use core::marker::PhantomData; | ||
| 5 | use embassy::util::Unborrow; | ||
| 6 | use embassy_hal_common::unborrow; | ||
| 7 | use stm32_metapac::timer::vals::Ocm; | ||
| 8 | |||
| 9 | pub struct Pwm<'d, T: Instance> { | ||
| 10 | phantom: PhantomData<&'d mut T>, | ||
| 11 | } | ||
| 12 | |||
| 13 | // TIM2 | ||
| 14 | |||
| 15 | pub struct Ch1 {} | ||
| 16 | pub struct Ch2 {} | ||
| 17 | pub struct Ch3 {} | ||
| 18 | pub struct Ch4 {} | ||
| 19 | |||
| 20 | #[derive(Clone, Copy)] | ||
| 21 | pub enum Channel { | ||
| 22 | Ch1, | ||
| 23 | Ch2, | ||
| 24 | Ch3, | ||
| 25 | Ch4, | ||
| 26 | } | ||
| 27 | |||
| 28 | impl<'d, T: Instance> Pwm<'d, T> { | ||
| 29 | pub fn new<F: Into<Hertz>>( | ||
| 30 | _tim: impl Unborrow<Target = T> + 'd, | ||
| 31 | ch1: impl Unborrow<Target = impl PwmPin<T, Ch1>> + 'd, | ||
| 32 | ch2: impl Unborrow<Target = impl PwmPin<T, Ch2>> + 'd, | ||
| 33 | ch3: impl Unborrow<Target = impl PwmPin<T, Ch3>> + 'd, | ||
| 34 | ch4: impl Unborrow<Target = impl PwmPin<T, Ch4>> + 'd, | ||
| 35 | freq: F, | ||
| 36 | ) -> Self { | ||
| 37 | unborrow!(ch1, ch2, ch3, ch4); | ||
| 38 | |||
| 39 | T::enable(); | ||
| 40 | T::reset(); | ||
| 41 | let r = T::regs(); | ||
| 42 | |||
| 43 | let mut this = Pwm { | ||
| 44 | phantom: PhantomData, | ||
| 45 | }; | ||
| 46 | unsafe { | ||
| 47 | ch1.configure(); | ||
| 48 | ch2.configure(); | ||
| 49 | ch3.configure(); | ||
| 50 | ch4.configure(); | ||
| 51 | } | ||
| 52 | |||
| 53 | unsafe { | ||
| 54 | use stm32_metapac::timer::vals::Dir; | ||
| 55 | this.set_freq(freq); | ||
| 56 | r.cr1().write(|w| { | ||
| 57 | w.set_cen(true); | ||
| 58 | w.set_dir(Dir::UP) | ||
| 59 | }); | ||
| 60 | |||
| 61 | this.set_ocm(Channel::Ch1, Ocm::PWMMODE1); | ||
| 62 | this.set_ocm(Channel::Ch2, Ocm::PWMMODE1); | ||
| 63 | this.set_ocm(Channel::Ch3, Ocm::PWMMODE1); | ||
| 64 | this.set_ocm(Channel::Ch4, Ocm::PWMMODE1); | ||
| 65 | } | ||
| 66 | this | ||
| 67 | } | ||
| 68 | |||
| 69 | unsafe fn set_ocm(&mut self, channel: Channel, mode: Ocm) { | ||
| 70 | let r = T::regs(); | ||
| 71 | match channel { | ||
| 72 | Channel::Ch1 => r.ccmr_output(0).modify(|w| w.set_ocm(0, mode)), | ||
| 73 | Channel::Ch2 => r.ccmr_output(0).modify(|w| w.set_ocm(1, mode)), | ||
| 74 | Channel::Ch3 => r.ccmr_output(1).modify(|w| w.set_ocm(0, mode)), | ||
| 75 | Channel::Ch4 => r.ccmr_output(1).modify(|w| w.set_ocm(1, mode)), | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | unsafe fn set_enable(&mut self, channel: Channel, enable: bool) { | ||
| 80 | let r = T::regs(); | ||
| 81 | match channel { | ||
| 82 | Channel::Ch1 => r.ccer().modify(|w| w.set_cce(0, enable)), | ||
| 83 | Channel::Ch2 => r.ccer().modify(|w| w.set_cce(1, enable)), | ||
| 84 | Channel::Ch3 => r.ccer().modify(|w| w.set_cce(2, enable)), | ||
| 85 | Channel::Ch4 => r.ccer().modify(|w| w.set_cce(3, enable)), | ||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | pub fn enable(&mut self, channel: Channel) { | ||
| 90 | unsafe { self.set_enable(channel, true) } | ||
| 91 | } | ||
| 92 | |||
| 93 | pub fn disable(&mut self, channel: Channel) { | ||
| 94 | unsafe { self.set_enable(channel, false) } | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn set_freq<F: Into<Hertz>>(&mut self, freq: F) { | ||
| 98 | use core::convert::TryInto; | ||
| 99 | let clk = T::frequency(); | ||
| 100 | let r = T::regs(); | ||
| 101 | let freq: Hertz = freq.into(); | ||
| 102 | let ticks: u32 = clk.0 / freq.0; | ||
| 103 | let psc: u16 = (ticks / (1 << 16)).try_into().unwrap(); | ||
| 104 | let arr: u16 = (ticks / (u32::from(psc) + 1)).try_into().unwrap(); | ||
| 105 | unsafe { | ||
| 106 | r.psc().write(|w| w.set_psc(psc)); | ||
| 107 | r.arr().write(|w| w.set_arr(arr)); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | pub fn get_max_duty(&self) -> u32 { | ||
| 112 | let r = T::regs(); | ||
| 113 | unsafe { r.arr().read().arr() as u32 } | ||
| 114 | } | ||
| 115 | |||
| 116 | pub fn set_duty(&mut self, channel: Channel, duty: u32) { | ||
| 117 | use core::convert::TryInto; | ||
| 118 | assert!(duty < self.get_max_duty()); | ||
| 119 | let duty: u16 = duty.try_into().unwrap(); | ||
| 120 | let r = T::regs(); | ||
| 121 | unsafe { | ||
| 122 | match channel { | ||
| 123 | Channel::Ch1 => r.ccr(0).modify(|w| w.set_ccr(duty)), | ||
| 124 | Channel::Ch2 => r.ccr(1).modify(|w| w.set_ccr(duty)), | ||
| 125 | Channel::Ch3 => r.ccr(2).modify(|w| w.set_ccr(duty)), | ||
| 126 | Channel::Ch4 => r.ccr(3).modify(|w| w.set_ccr(duty)), | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | pub(crate) mod sealed { | ||
| 133 | pub trait Instance { | ||
| 134 | fn regs() -> crate::pac::timer::TimGp16; | ||
| 135 | } | ||
| 136 | } | ||
| 137 | |||
| 138 | pub trait Instance: sealed::Instance + Sized + RccPeripheral + 'static {} | ||
| 139 | |||
| 140 | #[allow(unused)] | ||
| 141 | macro_rules! impl_timer { | ||
| 142 | ($inst:ident) => { | ||
| 143 | impl crate::pwm::sealed::Instance for crate::peripherals::$inst { | ||
| 144 | fn regs() -> crate::pac::timer::TimGp16 { | ||
| 145 | crate::pac::timer::TimGp16(crate::pac::$inst.0) | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | impl crate::pwm::Instance for crate::peripherals::$inst {} | ||
| 150 | }; | ||
| 151 | } | ||
| 152 | |||
| 153 | pub trait PwmPin<Timer, Channel>: gpio::OptionalPin { | ||
| 154 | unsafe fn configure(&mut self); | ||
| 155 | } | ||
| 156 | |||
| 157 | impl<Timer, Channel> PwmPin<Timer, Channel> for gpio::NoPin { | ||
| 158 | unsafe fn configure(&mut self) {} | ||
| 159 | } | ||
| 160 | |||
| 161 | #[allow(unused)] | ||
| 162 | macro_rules! impl_pwm_pin { | ||
| 163 | ($timer:ident, $channel:ident, $pin:ident, $af:expr) => { | ||
| 164 | impl crate::pwm::PwmPin<crate::peripherals::$timer, crate::pwm::$channel> | ||
| 165 | for crate::peripherals::$pin | ||
| 166 | { | ||
| 167 | unsafe fn configure(&mut self) { | ||
| 168 | use crate::gpio::sealed::{AFType, Pin}; | ||
| 169 | use crate::gpio::Speed; | ||
| 170 | self.set_low(); | ||
| 171 | self.set_speed(Speed::VeryHigh); | ||
| 172 | self.set_as_af($af, AFType::OutputPushPull); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | }; | ||
| 176 | } | ||
| 177 | |||
| 178 | crate::pac::peripherals!( | ||
| 179 | (timer, $inst:ident) => { impl_timer!($inst); }; | ||
| 180 | ); | ||
| 181 | |||
| 182 | crate::pac::peripheral_pins!( | ||
| 183 | ($inst:ident, timer,TIM_GP16, $pin:ident, CH1, $af:expr) => { | ||
| 184 | impl_pwm_pin!($inst, Ch1, $pin, $af); | ||
| 185 | }; | ||
| 186 | ($inst:ident, timer,TIM_GP16, $pin:ident, CH2, $af:expr) => { | ||
| 187 | impl_pwm_pin!($inst, Ch2, $pin, $af); | ||
| 188 | }; | ||
| 189 | ($inst:ident, timer,TIM_GP16, $pin:ident, CH3, $af:expr) => { | ||
| 190 | impl_pwm_pin!($inst, Ch3, $pin, $af); | ||
| 191 | }; | ||
| 192 | ($inst:ident, timer,TIM_GP16, $pin:ident, CH4, $af:expr) => { | ||
| 193 | impl_pwm_pin!($inst, Ch4, $pin, $af); | ||
| 194 | }; | ||
| 195 | ); | ||
diff --git a/examples/stm32g4/Cargo.toml b/examples/stm32g4/Cargo.toml index 0f9d77f5e..f4378309a 100644 --- a/examples/stm32g4/Cargo.toml +++ b/examples/stm32g4/Cargo.toml | |||
| @@ -8,7 +8,7 @@ resolver = "2" | |||
| 8 | [dependencies] | 8 | [dependencies] |
| 9 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt"] } | 9 | embassy = { version = "0.1.0", path = "../../embassy", features = ["defmt"] } |
| 10 | embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } | 10 | embassy-traits = { version = "0.1.0", path = "../../embassy-traits", features = ["defmt"] } |
| 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "time-driver-tim2", "stm32g491re", "memory-x", "unstable-pac"] } | 11 | embassy-stm32 = { version = "0.1.0", path = "../../embassy-stm32", features = ["defmt", "time-driver-tim3", "stm32g491re", "memory-x", "unstable-pac"] } |
| 12 | embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } | 12 | embassy-hal-common = {version = "0.1.0", path = "../../embassy-hal-common" } |
| 13 | 13 | ||
| 14 | defmt = "0.3" | 14 | defmt = "0.3" |
diff --git a/examples/stm32g4/src/bin/pwm.rs b/examples/stm32g4/src/bin/pwm.rs new file mode 100644 index 000000000..1aa7b85f3 --- /dev/null +++ b/examples/stm32g4/src/bin/pwm.rs | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | #[path = "../example_common.rs"] | ||
| 6 | mod example_common; | ||
| 7 | use embassy::executor::Spawner; | ||
| 8 | use embassy::time::{Duration, Timer}; | ||
| 9 | use embassy_stm32::gpio::NoPin; | ||
| 10 | use embassy_stm32::pwm::{Channel, Pwm}; | ||
| 11 | use embassy_stm32::time::U32Ext; | ||
| 12 | use embassy_stm32::Peripherals; | ||
| 13 | use example_common::*; | ||
| 14 | |||
| 15 | #[embassy::main] | ||
| 16 | async fn main(_spawner: Spawner, p: Peripherals) { | ||
| 17 | info!("Hello World!"); | ||
| 18 | |||
| 19 | let mut pwm = Pwm::new(p.TIM2, p.PA5, NoPin, NoPin, NoPin, 10000.hz()); | ||
| 20 | let max = pwm.get_max_duty(); | ||
| 21 | pwm.enable(Channel::Ch1); | ||
| 22 | |||
| 23 | info!("PWM initialized"); | ||
| 24 | info!("PWM max duty {}", max); | ||
| 25 | |||
| 26 | loop { | ||
| 27 | pwm.set_duty(Channel::Ch1, 0); | ||
| 28 | Timer::after(Duration::from_millis(300)).await; | ||
| 29 | pwm.set_duty(Channel::Ch1, max / 4); | ||
| 30 | Timer::after(Duration::from_millis(300)).await; | ||
| 31 | pwm.set_duty(Channel::Ch1, max / 2); | ||
| 32 | Timer::after(Duration::from_millis(300)).await; | ||
| 33 | pwm.set_duty(Channel::Ch1, max - 1); | ||
| 34 | Timer::after(Duration::from_millis(300)).await; | ||
| 35 | } | ||
| 36 | } | ||
