summaryrefslogtreecommitdiffstats
path: root/src/pulse.rs
diff options
context:
space:
mode:
authorTomasz Kramkowski <tomasz@kramkow.ski>2025-10-16 19:33:17 +0100
committerTomasz Kramkowski <tomasz@kramkow.ski>2025-10-16 19:33:17 +0100
commitcdf2a5026e45ac2b65001ed41cc67d86590a681a (patch)
tree56fda9ee1cc0159e725952802547e5e6d327428e /src/pulse.rs
downloadscale-drv-example-master.tar.gz
scale-drv-example-master.tar.xz
scale-drv-example-master.zip
Driver exampleHEADmaster
Diffstat (limited to 'src/pulse.rs')
-rw-r--r--src/pulse.rs119
1 files changed, 119 insertions, 0 deletions
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 <tomasz@kramkow.ski>
+// 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<CountingMode> 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<PwmPin<'d, T, Ch1>>,
+ ch2: Option<PwmPin<'d, T, Ch2>>,
+ ch3: Option<PwmPin<'d, T, Ch3>>,
+ ch4: Option<PwmPin<'d, T, Ch4>>,
+ period: Hertz,
+ counting_mode: CountingMode,
+ irq: impl Binding<T::UpdateInterrupt, UpdateInterruptHandler<T>> + 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()
+ }
+}