embassy_stm32/timer/
qei.rs

1//! Quadrature decoder using a timer.
2
3use core::marker::PhantomData;
4
5use stm32_metapac::timer::vals;
6
7use super::low_level::Timer;
8pub use super::{Ch1, Ch2};
9use super::{GeneralInstance4Channel, TimerPin};
10use crate::gpio::{AfType, AnyPin, Pull};
11use crate::timer::TimerChannel;
12use crate::Peri;
13
14/// Counting direction
15pub enum Direction {
16    /// Counting up.
17    Upcounting,
18    /// Counting down.
19    Downcounting,
20}
21
22/// Wrapper for using a pin with QEI.
23pub struct QeiPin<'d, T, Channel> {
24    _pin: Peri<'d, AnyPin>,
25    phantom: PhantomData<(T, Channel)>,
26}
27
28impl<'d, T: GeneralInstance4Channel, C: QeiChannel> QeiPin<'d, T, C> {
29    /// Create a new  QEI pin instance.
30    pub fn new(pin: Peri<'d, impl TimerPin<T, C>>) -> Self {
31        critical_section::with(|_| {
32            pin.set_low();
33            pin.set_as_af(pin.af_num(), AfType::input(Pull::None));
34        });
35        QeiPin {
36            _pin: pin.into(),
37            phantom: PhantomData,
38        }
39    }
40}
41
42trait SealedQeiChannel: TimerChannel {}
43
44/// Marker trait for a timer channel eligible for use with QEI.
45#[expect(private_bounds)]
46pub trait QeiChannel: SealedQeiChannel {}
47
48impl QeiChannel for Ch1 {}
49impl QeiChannel for Ch2 {}
50
51impl SealedQeiChannel for Ch1 {}
52impl SealedQeiChannel for Ch2 {}
53
54/// Quadrature decoder driver.
55pub struct Qei<'d, T: GeneralInstance4Channel> {
56    inner: Timer<'d, T>,
57}
58
59impl<'d, T: GeneralInstance4Channel> Qei<'d, T> {
60    /// Create a new quadrature decoder driver.
61    pub fn new(tim: Peri<'d, T>, _ch1: QeiPin<'d, T, Ch1>, _ch2: QeiPin<'d, T, Ch2>) -> Self {
62        Self::new_inner(tim)
63    }
64
65    fn new_inner(tim: Peri<'d, T>) -> Self {
66        let inner = Timer::new(tim);
67        let r = inner.regs_gp16();
68
69        // Configure TxC1 and TxC2 as captures
70        r.ccmr_input(0).modify(|w| {
71            w.set_ccs(0, vals::CcmrInputCcs::TI4);
72            w.set_ccs(1, vals::CcmrInputCcs::TI4);
73        });
74
75        // enable and configure to capture on rising edge
76        r.ccer().modify(|w| {
77            w.set_cce(0, true);
78            w.set_cce(1, true);
79
80            w.set_ccp(0, false);
81            w.set_ccp(1, false);
82        });
83
84        r.smcr().modify(|w| {
85            w.set_sms(vals::Sms::ENCODER_MODE_3);
86        });
87
88        r.arr().modify(|w| w.set_arr(u16::MAX));
89        r.cr1().modify(|w| w.set_cen(true));
90
91        Self { inner }
92    }
93
94    /// Get direction.
95    pub fn read_direction(&self) -> Direction {
96        match self.inner.regs_gp16().cr1().read().dir() {
97            vals::Dir::DOWN => Direction::Downcounting,
98            vals::Dir::UP => Direction::Upcounting,
99        }
100    }
101
102    /// Get count.
103    pub fn count(&self) -> u16 {
104        self.inner.regs_gp16().cnt().read().cnt()
105    }
106}