// 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() } }