Skip to main content

ra_hal/
qdec.rs

1//! Quadrature decoder driver (`GPT`) utilizing the General PWM Timer (`GPT`).
2//!
3//! The `GPT` peripheral provides support for what Renesas calls "phase counting" a.k.a. quadrature decoding.
4//! This driver configures a `GPT` instance for "phase counting mode 1" (Table 22.7).
5//! In mode 1, the following events are recognized as up-counting events:
6//! - Rising edge on pin A while pin B is low
7//! - Falling edge on pin A while pin B is high
8//! - Rising edge on pin B while pin A is high
9//! - Falling edge on pin B while pin A is low
10//!
11//! In mode 1, the following events are recognized as down-counting events:
12//! - Rising edge on pin A while pin B is high
13//! - Falling edge on pin A while pin B is low
14//! - Rising edge on pin B while pin A is low
15//! - Falling edge on pin B while pin A is high
16//!
17//! # TODO
18//! - Support other phase counting modes
19//! - Handle over/underflow (or verify correct operation)
20
21use core::{future::poll_fn, marker::PhantomData, task::Poll};
22
23use embassy_hal_internal::{Peri, interrupt::InterruptExt as _};
24
25use crate::{
26    event_link::IcuInterrupt as _,
27    gpio::{Flex, WithOpenDrain},
28    interrupt,
29    interrupt::typelevel::{Handler as InterruptHandler, Interrupt as InterruptType},
30    pac::gpt::{
31        regs::{Gtdnsr, Gtupsr},
32        vals::{Ccr, Nfcs},
33    },
34    pwm::{ChanA, ChanB, Divider, Instance, PwmPin},
35};
36
37/// Quadrature decoder configuration
38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
39#[non_exhaustive]
40pub struct Config {
41    /// Clock divider for GPT instance (`PCLKD/n`).
42    pub divider: Divider,
43
44    /// Debounce filter configuration.
45    pub debounce: Debounce,
46}
47
48/// Noise (debounce) filter for channels A and B.
49#[cfg_attr(feature = "defmt", derive(defmt::Format))]
50#[derive(Copy, Clone, PartialEq)]
51pub enum Debounce {
52    /// Noise filter is disabled.
53    Off,
54
55    /// Event must last for at least 1 `PCLKD` cycle.
56    Min1,
57
58    /// Event must last for at least 4 `PCLKD` cycles.
59    Min4,
60
61    /// Event must last for at least 16 `PCLKD` cycles.
62    Min16,
63
64    /// Event must last for at least 64 `PCLKD` cycles.
65    Min64,
66}
67
68/// Direction of rotation
69#[cfg_attr(feature = "defmt", derive(defmt::Format))]
70#[derive(Copy, Clone, PartialEq)]
71pub enum Direction {
72    #[allow(missing_docs)]
73    Up,
74
75    #[allow(missing_docs)]
76    Down,
77
78    #[allow(missing_docs)]
79    Unknown,
80}
81
82/// Quadrature decoder driver for the `GPT` timer.
83#[allow(private_bounds)]
84pub struct Qdec<'d, I: Instance, Int: InterruptType> {
85    _instance: PhantomData<&'d I>,
86    _interrupt: PhantomData<Int>,
87    // These are set to WithOpenDrain because on the RA4M1 all PWM pins have both capabilities
88    _pin_a: Flex<'d, WithOpenDrain>,
89    _pin_b: Flex<'d, WithOpenDrain>,
90}
91
92/// Interrupt handler for rotation event.
93#[allow(private_bounds)]
94pub struct QdecInterruptHandler<I: Instance> {
95    _phantom: PhantomData<I>,
96}
97
98impl From<Debounce> for Nfcs {
99    fn from(value: Debounce) -> Self {
100        match value {
101            // This is not actually off, to do that we need to poke a different register
102            Debounce::Off => Self::Pclkd1,
103            Debounce::Min1 => Self::Pclkd1,
104            Debounce::Min4 => Self::Pclkd4,
105            Debounce::Min16 => Self::Pclkd16,
106            Debounce::Min64 => Self::Pclkd64,
107        }
108    }
109}
110
111impl Default for Config {
112    fn default() -> Self {
113        Self {
114            divider: Divider::Div1024,
115            debounce: Debounce::Min64,
116        }
117    }
118}
119
120impl<I: Instance, Int: InterruptType> InterruptHandler<Int> for QdecInterruptHandler<I> {
121    unsafe fn on_interrupt() {
122        Int::IRQ.icu_unpend();
123        I::cmpa_waker().wake()
124    }
125}
126
127#[allow(private_bounds)]
128impl<'d, I: Instance, Int: InterruptType> Qdec<'d, I, Int> {
129    /// Consumes a `GPT` peripheral instance and returns a quadrature decoder driver.
130    pub fn new<A: PwmPin<I, ChanA>, B: PwmPin<I, ChanB>>(
131        peri: Peri<'d, I>,
132        pin_a: Peri<'d, A>,
133        pin_b: Peri<'d, B>,
134        config: Config,
135        irq: impl interrupt::typelevel::Binding<Int, QdecInterruptHandler<I>>,
136    ) -> Self {
137        let _ = peri;
138        let _ = irq;
139
140        let pwm = I::regs();
141
142        pwm.gticasr().modify(|r| {
143            r.set_ascarbl(true);
144            r.set_ascarbh(false);
145            r.set_ascafbl(true);
146            r.set_ascafbh(false);
147        });
148        pwm.gtcr().modify(|w| w.set_tpcs(config.divider.into()));
149        pwm.gtpr().write_value(0xFFFF);
150
151        // These were taken from the manual ยง 22.3.10.
152        pwm.gtupsr().write_value(Gtupsr(0x6900));
153        pwm.gtdnsr().write_value(Gtdnsr(0x9600));
154
155        let debounce_enable = config.debounce != Debounce::Off;
156
157        // Debouncing. Maybe.
158        pwm.gtior().modify(|r| {
159            r.set_nfaen(debounce_enable);
160            r.set_nfcsa(config.debounce.into());
161            r.set_nfben(debounce_enable);
162            r.set_nfcsb(config.debounce.into());
163        });
164
165        pwm.gtcnt().write_value(0);
166        pwm.gtccra().write_value(0);
167        pwm.gtccrc().write_value(0);
168        pwm.gtber().modify(|w| w.set_ccra(Ccr::SingleBuffer));
169
170        pin_a.set_pfunc();
171        let pin_a = Flex::new(pin_a);
172
173        pin_b.set_pfunc();
174        let pin_b = Flex::new(pin_b);
175
176        unsafe {
177            Int::IRQ.enable();
178            Int::IRQ.icu_enable(I::CAPTURE_COMP_A_EVENT)
179        };
180
181        pwm.gtcr().modify(|w| w.set_cst(true));
182        pwm.gtst().modify(|r| r.set_tcfa(false));
183
184        Self {
185            _instance: PhantomData,
186            _interrupt: PhantomData,
187            _pin_a: pin_a,
188            _pin_b: pin_b,
189        }
190    }
191
192    /// Returns the current count.
193    pub fn count(&self) -> u32 {
194        let pwm = I::regs();
195        // Let's consider not all instances are 32-bits wide
196        pwm.gtcnt().read()
197    }
198
199    /// Waits for a rotation event and returns the direction of rotation.
200    pub async fn read(&self) -> Direction {
201        let pwm = I::regs();
202
203        poll_fn(|ctx| {
204            I::cmpa_waker().register(ctx.waker());
205
206            if pwm.gtst().read().tcfa() {
207                // Clear the event
208                pwm.gtst().modify(|r| r.set_tcfa(false));
209
210                let count = pwm.gtcnt().read();
211                let prev_count = pwm.gtccra().read();
212
213                if count > prev_count {
214                    Poll::Ready(Direction::Up)
215                } else if count < prev_count {
216                    Poll::Ready(Direction::Down)
217                } else {
218                    Poll::Ready(Direction::Unknown)
219                }
220            } else {
221                Poll::Pending
222            }
223        })
224        .await
225    }
226
227    /// Resets the counter and buffers to zero.
228    pub fn reset(&mut self) {
229        let pwm = I::regs();
230
231        critical_section::with(|_| {
232            pwm.gtcnt().write_value(0);
233            pwm.gtccra().write_value(0);
234            pwm.gtccrc().write_value(0);
235        });
236    }
237}
238
239impl<'d, I: Instance, Int: InterruptType> Drop for Qdec<'d, I, Int> {
240    /// Note: this will unlink the interrupt and stop the timer but does not turn off the GPT clock.
241    fn drop(&mut self) {
242        Int::IRQ.disable();
243        Int::IRQ.icu_disable();
244
245        let pwm = I::regs();
246        pwm.gtcr().modify(|w| w.set_cst(false));
247    }
248}