diff options
| -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 | } | ||
