diff --git a/embassy-stm32/src/timer/input_capture.rs b/embassy-stm32/src/timer/input_capture.rs index 000938a70..b3434ae63 100644 --- a/embassy-stm32/src/timer/input_capture.rs +++ b/embassy-stm32/src/timer/input_capture.rs @@ -80,18 +80,18 @@ impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { } fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, counting_mode: CountingMode) -> Self { - let mut this = Self { inner: Timer::new(tim) }; + let mut inner = Timer::new(tim); - this.inner.set_counting_mode(counting_mode); - this.set_tick_freq(freq); - this.inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details - this.inner.start(); + inner.set_counting_mode(counting_mode); + inner.set_tick_freq(freq); + inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + inner.start(); // enable NVIC interrupt T::CaptureCompareInterrupt::unpend(); unsafe { T::CaptureCompareInterrupt::enable() }; - this + Self { inner } } /// Enable the given channel. @@ -109,24 +109,6 @@ impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { self.inner.get_channel_enable_state(channel) } - /// Set tick frequency. - /// - /// Note: when you call this, the max period value changes - pub fn set_tick_freq(&mut self, freq: Hertz) { - let f = freq; - assert!(f.0 > 0); - let timer_f = self.inner.get_clock_frequency(); - - let pclk_ticks_per_timer_period = timer_f / f; - let psc: u16 = unwrap!((pclk_ticks_per_timer_period - 1).try_into()); - - let regs = self.inner.regs_core(); - regs.psc().write_value(psc); - - // Generate an Update Request - regs.egr().write(|r| r.set_ug(true)); - } - /// Set the input capture mode for a given channel. pub fn set_input_capture_mode(&mut self, channel: Channel, mode: InputCaptureMode) { self.inner.set_input_capture_mode(channel, mode); @@ -150,7 +132,8 @@ impl<'d, T: GeneralInstance4Channel> InputCapture<'d, T> { fn new_future(&self, channel: Channel, mode: InputCaptureMode, tisel: InputTISelection) -> InputCaptureFuture { use stm32_metapac::timer::vals::FilterValue; - // Configuration steps from ST RM0390 chapter 17.3.5 Input Capture Mode + // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.5 + // or ST RM0008 (STM32F103) chapter 15.3.5 Input capture mode self.inner.set_input_ti_selection(channel, tisel); self.inner.set_input_capture_filter(channel, FilterValue::NOFILTER); self.inner.set_input_capture_mode(channel, mode); diff --git a/embassy-stm32/src/timer/low_level.rs b/embassy-stm32/src/timer/low_level.rs index 7f533b75c..141e96894 100644 --- a/embassy-stm32/src/timer/low_level.rs +++ b/embassy-stm32/src/timer/low_level.rs @@ -273,6 +273,22 @@ impl<'d, T: CoreInstance> Timer<'d, T> { } } + /// Set tick frequency. + pub fn set_tick_freq(&mut self, freq: Hertz) { + let f = freq; + assert!(f.0 > 0); + let timer_f = self.get_clock_frequency(); + + let pclk_ticks_per_timer_period = timer_f / f; + let psc: u16 = unwrap!((pclk_ticks_per_timer_period - 1).try_into()); + + let regs = self.regs_core(); + regs.psc().write_value(psc); + + // Generate an Update Request + regs.egr().write(|r| r.set_ug(true)); + } + /// Clear update interrupt. /// /// Returns whether the update interrupt flag was set. diff --git a/embassy-stm32/src/timer/mod.rs b/embassy-stm32/src/timer/mod.rs index 314b6006b..25782ee13 100644 --- a/embassy-stm32/src/timer/mod.rs +++ b/embassy-stm32/src/timer/mod.rs @@ -8,6 +8,7 @@ use embassy_sync::waitqueue::AtomicWaker; pub mod complementary_pwm; pub mod input_capture; pub mod low_level; +pub mod pwm_input; pub mod qei; pub mod simple_pwm; diff --git a/embassy-stm32/src/timer/pwm_input.rs b/embassy-stm32/src/timer/pwm_input.rs new file mode 100644 index 000000000..d34ba086f --- /dev/null +++ b/embassy-stm32/src/timer/pwm_input.rs @@ -0,0 +1,134 @@ +//! Input capture driver. + +use embassy_hal_internal::into_ref; + +use super::low_level::{CountingMode, InputCaptureMode, InputTISelection, Timer}; +use super::{Channel, Channel1Pin, Channel2Pin, GeneralInstance4Channel}; +use crate::gpio::{AFType, Pull}; +use crate::time::Hertz; +use crate::Peripheral; + +/// Input capture driver. +pub struct PwmInput<'d, T: GeneralInstance4Channel> { + channel: Channel, + inner: Timer<'d, T>, +} + +/// Convert pointer to TIM instance to TimGp16 object +fn regs_gp16(ptr: *mut ()) -> crate::pac::timer::TimGp16 { + unsafe { crate::pac::timer::TimGp16::from_ptr(ptr) } +} + +impl<'d, T: GeneralInstance4Channel> PwmInput<'d, T> { + /// Create a new input capture driver. + pub fn new( + tim: impl Peripheral

+ 'd, + pin: impl Peripheral

> + 'd, + pull_type: Pull, + freq: Hertz, + ) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_as_af_pull(pin.af_num(), AFType::Input, pull_type); + #[cfg(gpio_v2)] + pin.set_speed(crate::gpio::Speed::VeryHigh); + }); + + Self::new_inner(tim, freq, Channel::Ch1, Channel::Ch2) + } + + /// Create a new input capture driver. + pub fn new_alt( + tim: impl Peripheral

+ 'd, + pin: impl Peripheral

> + 'd, + pull_type: Pull, + freq: Hertz, + ) -> Self { + into_ref!(pin); + critical_section::with(|_| { + pin.set_as_af_pull(pin.af_num(), AFType::Input, pull_type); + #[cfg(gpio_v2)] + pin.set_speed(crate::gpio::Speed::VeryHigh); + }); + + Self::new_inner(tim, freq, Channel::Ch2, Channel::Ch1) + } + + fn new_inner(tim: impl Peripheral

+ 'd, freq: Hertz, ch1: Channel, ch2: Channel) -> Self { + use stm32_metapac::timer::vals::{Sms, Ts}; + + let mut inner = Timer::new(tim); + + inner.set_counting_mode(CountingMode::EdgeAlignedUp); + inner.set_tick_freq(freq); + inner.enable_outputs(); // Required for advanced timers, see GeneralInstance4Channel for details + inner.start(); + + // Configuration steps from ST RM0390 (STM32F446) chapter 17.3.6 + // or ST RM0008 (STM32F103) chapter 15.3.6 Input capture mode + inner.set_input_ti_selection(ch1, InputTISelection::Normal); + inner.set_input_capture_mode(ch1, InputCaptureMode::Rising); + + inner.set_input_ti_selection(ch2, InputTISelection::Alternate); + inner.set_input_capture_mode(ch2, InputCaptureMode::Falling); + + let regs = regs_gp16(T::regs()); + regs.smcr().modify(|r| { + // Select the valid trigger input: write the TS bits to 101 in the TIMx_SMCR register + // (TI1FP1 selected). + r.set_ts(match ch1 { + Channel::Ch1 => Ts::TI1FP1, + Channel::Ch2 => Ts::TI2FP2, + _ => panic!("Invalid channel for PWM input"), + }); + + // Configure the slave mode controller in reset mode: write the SMS bits to 100 in the + // TIMx_SMCR register. + r.set_sms(Sms::RESET_MODE); + }); + + // Must call the `enable` function after + + Self { channel: ch1, inner } + } + + /// Enable the given channel. + pub fn enable(&mut self) { + self.inner.enable_channel(Channel::Ch1, true); + self.inner.enable_channel(Channel::Ch2, true); + } + + /// Disable the given channel. + pub fn disable(&mut self) { + self.inner.enable_channel(Channel::Ch1, false); + self.inner.enable_channel(Channel::Ch2, false); + } + + /// Check whether given channel is enabled + pub fn is_enabled(&self) -> bool { + self.inner.get_channel_enable_state(Channel::Ch1) + } + + /// Get the period tick count + pub fn get_period_ticks(&self) -> u32 { + self.inner.get_capture_value(self.channel) + } + + /// Get the duty tick count + pub fn get_duty_ticks(&self) -> u32 { + self.inner.get_capture_value(match self.channel { + Channel::Ch1 => Channel::Ch2, + Channel::Ch2 => Channel::Ch1, + _ => panic!("Invalid channel for PWM input"), + }) + } + + /// Get the duty cycle in 100% + pub fn get_duty_cycle(&self) -> f32 { + let period = self.get_period_ticks(); + if period == 0 { + return 0.; + } + 100. * (self.get_duty_ticks() as f32) / (period as f32) + } +} diff --git a/examples/stm32f1/.vscode/launch.json b/examples/stm32f1/.vscode/launch.json index 7d1504a39..998508867 100644 --- a/examples/stm32f1/.vscode/launch.json +++ b/examples/stm32f1/.vscode/launch.json @@ -15,7 +15,7 @@ "cwd": "${workspaceRoot}", "preLaunchTask": "Cargo Build (debug)", "runToEntryPoint": "main", - "executable": "./target/thumbv7m-none-eabi/debug/input_capture", + "executable": "./target/thumbv7m-none-eabi/debug/pwm_input", /* Run `cargo build --example itm` and uncomment this line to run itm example */ // "executable": "./target/thumbv7em-none-eabihf/debug/examples/itm", "device": "STM32F103TB", diff --git a/examples/stm32f1/.vscode/tasks.json b/examples/stm32f1/.vscode/tasks.json index e153722da..de7013b12 100644 --- a/examples/stm32f1/.vscode/tasks.json +++ b/examples/stm32f1/.vscode/tasks.json @@ -9,7 +9,7 @@ ], "args": [ "--bin", - "input_capture" + "pwm_input" ], "group": { "kind": "build", diff --git a/examples/stm32f1/src/bin/pwm_input.rs b/examples/stm32f1/src/bin/pwm_input.rs new file mode 100644 index 000000000..14978f817 --- /dev/null +++ b/examples/stm32f1/src/bin/pwm_input.rs @@ -0,0 +1,50 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::{self, pwm_input::PwmInput}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Connect PB2 and PB10 with a 1k Ohm resistor + +#[embassy_executor::task] +async fn blinky(led: peripherals::PC13) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + unwrap!(spawner.spawn(blinky(p.PC13))); + + let pwm_input = PwmInput::new(p.TIM2, p.PA0, Pull::None, khz(1000)); + + loop { + Timer::after_millis(500).await; + let _per = pwm_input.get_period_ticks(); + let _dc = pwm_input.get_duty_ticks(); + let _pc = pwm_input.get_duty_cycle(); + asm::nop(); + } +} diff --git a/examples/stm32f4/.vscode/launch.json b/examples/stm32f4/.vscode/launch.json index 20cd4d2e8..a9849e0da 100644 --- a/examples/stm32f4/.vscode/launch.json +++ b/examples/stm32f4/.vscode/launch.json @@ -15,7 +15,7 @@ "cwd": "${workspaceRoot}", "preLaunchTask": "Cargo Build (debug)", "runToEntryPoint": "main", - "executable": "./target/thumbv7em-none-eabihf/debug/input_capture", + "executable": "./target/thumbv7em-none-eabihf/debug/pwm_input", /* Run `cargo build --example itm` and uncomment this line to run itm example */ // "executable": "./target/thumbv7em-none-eabihf/debug/examples/itm", "device": "STM32F446RET6", diff --git a/examples/stm32f4/.vscode/tasks.json b/examples/stm32f4/.vscode/tasks.json index e153722da..de7013b12 100644 --- a/examples/stm32f4/.vscode/tasks.json +++ b/examples/stm32f4/.vscode/tasks.json @@ -9,7 +9,7 @@ ], "args": [ "--bin", - "input_capture" + "pwm_input" ], "group": { "kind": "build", diff --git a/examples/stm32f4/src/bin/pwm_input.rs b/examples/stm32f4/src/bin/pwm_input.rs new file mode 100644 index 000000000..e57e58c22 --- /dev/null +++ b/examples/stm32f4/src/bin/pwm_input.rs @@ -0,0 +1,52 @@ +#![no_std] +#![no_main] + +use cortex_m::asm; +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Pull, Speed}; +use embassy_stm32::time::khz; +use embassy_stm32::timer::{self, pwm_input::PwmInput}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +/// Connect PB2 and PB10 with a 1k Ohm resistor + +#[embassy_executor::task] +async fn blinky(led: peripherals::PB2) { + let mut led = Output::new(led, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} + +bind_interrupts!(struct Irqs { + TIM2 => timer::CaptureCompareInterruptHandler; +}); + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + unwrap!(spawner.spawn(blinky(p.PB2))); + + let mut pwm_input = PwmInput::new(p.TIM3, p.PA6, Pull::None, khz(10)); + pwm_input.enable(); + + loop { + Timer::after_millis(500).await; + let _per = pwm_input.get_period_ticks(); + let _dc = pwm_input.get_duty_ticks(); + let _pc = pwm_input.get_duty_cycle(); + asm::nop(); + } +}