summaryrefslogtreecommitdiffstats
path: root/src/pulse.rs
blob: 0fbb40957fe39101b03ea83206669df680400600 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
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()
    }
}