From cdf2a5026e45ac2b65001ed41cc67d86590a681a Mon Sep 17 00:00:00 2001 From: Tomasz Kramkowski Date: Thu, 16 Oct 2025 19:33:17 +0100 Subject: Driver example --- src/pulse.rs | 119 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/pulse.rs (limited to 'src/pulse.rs') diff --git a/src/pulse.rs b/src/pulse.rs new file mode 100644 index 0000000..0fbb409 --- /dev/null +++ b/src/pulse.rs @@ -0,0 +1,119 @@ +// SPDX-FileCopyrightText: 2024-2025 Tomasz Kramkowski +// SPDX-License-Identifier: GPL-3.0-or-later + +use embassy_stm32::{ + interrupt::typelevel::{Binding, Interrupt}, + pac::timer::vals, + time::Hertz, + timer::{ + low_level::{self, mode, OutputCompareMode, OutputPolarity, Timer}, + simple_pwm::PwmPin, + Ch1, Ch2, Ch3, Ch4, Channel, GeneralInstance4Channel, UpdateInterruptHandler, + }, + Peri, +}; + +/// The counting mode setting for the timer +pub enum CountingMode { + EdgeAlignedUp, + EdgeAlignedDown, +} + +impl From for low_level::CountingMode { + fn from(value: CountingMode) -> Self { + match value { + CountingMode::EdgeAlignedUp => low_level::CountingMode::EdgeAlignedUp, + CountingMode::EdgeAlignedDown => low_level::CountingMode::EdgeAlignedDown, + } + } +} + +/// A clock pulse wrapper driver based on STM32 4ch Timers +pub struct Pulse<'d, T: GeneralInstance4Channel> { + inner: Timer<'d, T, mode::WithIrq>, +} + +impl<'d, T: GeneralInstance4Channel> Pulse<'d, T> { + /// Create a new Pulse driver from the timer, pin channels, and + /// configuration. + pub fn new( + tim: Peri<'d, T>, + ch1: Option>, + ch2: Option>, + ch3: Option>, + ch4: Option>, + period: Hertz, + counting_mode: CountingMode, + irq: impl Binding> + Copy + 'd, + ) -> Self { + let this = Self { + inner: Timer::new_with_interrupt(tim, irq), + }; + + this.inner.set_counting_mode(counting_mode.into()); + + // Configure OPM + this.inner.regs_gp16().cr1().modify(|r| r.set_opm(true)); + + this.inner.set_frequency(period); + + this.inner.enable_outputs(); + + // Calculate 50% duty + let half_duty = this.inner.get_max_compare_value().div_ceil(2); + + // Enable channels + [ + (Channel::Ch1, ch1.is_some()), + (Channel::Ch2, ch2.is_some()), + (Channel::Ch3, ch3.is_some()), + (Channel::Ch4, ch4.is_some()), + ] + .iter() + .filter_map(|(channel, present)| present.then_some(channel)) + .for_each(|&channel| { + // In OPM, Mode1 + ActiveLow and Mode2 + ActiveHigh are identical + this.inner + .set_output_compare_mode(channel, OutputCompareMode::PwmMode1); + this.inner + .set_output_polarity(channel, OutputPolarity::ActiveLow); + + // Configure duty cycle + this.inner.set_output_compare_preload(channel, true); + this.inner.set_compare_value(channel, half_duty); + + this.inner.enable_channel(channel, true); + }); + + // Initialise the counter registers + { + let regs = this.inner.regs_core(); + // Disable update interrupt generation + regs.cr1().modify(|r| r.set_urs(vals::Urs::COUNTER_ONLY)); + // Force shadow registers to update + regs.egr().write(|r| r.set_ug(true)); + // Re-enable interrupt generation + regs.cr1().modify(|r| r.set_urs(vals::Urs::ANY_EVENT)); + } + + // NVIC Configuration + T::UpdateInterrupt::unpend(); + unsafe { T::UpdateInterrupt::enable() }; + + this + } + + /// Trigger the pulse on all channel pins, wait for it to finish + pub async fn trigger_and_wait(&mut self) { + // TODO: Needed? + self.inner.clear_update_interrupt(); + self.inner.start(); + self.inner.wait_for_update().await; + } +} + +impl<'d, T: GeneralInstance4Channel> Drop for Pulse<'d, T> { + fn drop(&mut self) { + T::UpdateInterrupt::disable() + } +} -- cgit v1.2.3-70-g09d2