omnitiles/hw/
encoder.rs

1// SPDX-License-Identifier: MIT
2// © 2025–2026 Christopher Liu
3
4//! Quadrature encoder support via STM32F7 timers in encoder mode.
5//!
6//! This module configures TIM2 (32-bit) and TIM3 (16-bit) reigsters for encoder mode and provides
7//! simple accessors.
8
9use stm32f7xx_hal::pac;
10
11/// Generic encoder wrapper over a PAC TIMx peripheral.
12pub struct Encoder<TIM> {
13    tim: TIM,
14}
15
16impl<TIM> Encoder<TIM> {
17    /// Consume the wrapper and return the underlying timer peripheral.
18    #[inline]
19    pub fn free(self) -> TIM {
20        self.tim
21    }
22}
23
24impl Encoder<pac::TIM2> {
25    /// Configure TIM2 as a quadrature encoder with full 32-bit range.
26    pub fn tim2(tim2: pac::TIM2) -> Self {
27        let tim = tim2;
28
29        // Disable counter while configuring
30        tim.cr1.modify(|_, w| w.cen().clear_bit());
31
32        // Auto-reload: max 32-bit
33        tim.arr.write(|w| w.bits(0xFFFF_FFFF));
34
35        // Slave mode: encoder mode 3 (count on both TI1 and TI2)
36        tim.smcr.modify(|_, w| w.sms().bits(0b011));
37
38        // Configure CH1/CH2 as inputs from TI1/TI2
39        tim.ccmr1_input().modify(|_, w| w.cc1s().ti1().cc2s().ti2());
40
41        // Polarity and enable for both channels.
42        tim.ccer.modify(|_, w| {
43            w.cc1p()
44                .clear_bit()
45                .cc2p()
46                .clear_bit()
47                .cc1e()
48                .set_bit()
49                .cc2e()
50                .set_bit()
51        });
52
53        // Reset the counter
54        tim.cnt.write(|w| w.bits(0));
55
56        // Enable the counter
57        tim.cr1.modify(|_, w| w.cen().set_bit());
58
59        Self { tim }
60    }
61
62    /// Read the raw 32-bit counter value.
63    #[inline]
64    pub fn raw(&self) -> u32 {
65        self.tim.cnt.read().cnt().bits()
66    }
67
68    /// Interpret the counter as a signed 32-bit position.
69    #[inline]
70    pub fn position(&self) -> i32 {
71        self.raw() as i32
72    }
73
74    /// Reset the encoder position to zero.
75    #[inline]
76    pub fn reset(&mut self) {
77        self.tim.cnt.write(|w| w.bits(0));
78    }
79}
80
81impl Encoder<pac::TIM3> {
82    /// Configure TIM3 as a quadrature encoder with full 16-bit range.
83    pub fn tim3(tim3: pac::TIM3) -> Self {
84        let tim = tim3;
85
86        // Disable counter while configuring
87        tim.cr1.modify(|_, w| w.cen().clear_bit());
88
89        // Auto-reload: max 16-bit
90        tim.arr.write(|w| unsafe { w.bits(0xFFFF) });
91
92        // Slave mode: encoder mode 3 (count on both TI1 and TI2)
93        tim.smcr.modify(|_, w| w.sms().bits(0b011));
94
95        // Configure CH1/CH2 as inputs from TI1/TI2
96        tim.ccmr1_input().modify(|_, w| w.cc1s().ti1().cc2s().ti2());
97
98        // Polarity and enable for both channels.
99        tim.ccer.modify(|_, w| {
100            w.cc1p()
101                .clear_bit()
102                .cc2p()
103                .clear_bit()
104                .cc1e()
105                .set_bit()
106                .cc2e()
107                .set_bit()
108        });
109
110        // Reset counter
111        tim.cnt.write(|w| unsafe { w.bits(0) });
112
113        // Enable counter
114        tim.cr1.modify(|_, w| w.cen().set_bit());
115
116        Self { tim }
117    }
118
119    /// Read the raw 16-bit counter value.
120    #[inline]
121    pub fn raw(&self) -> u16 {
122        self.tim.cnt.read().cnt().bits()
123    }
124
125    /// Interpret the counter as a signed 16-bit position.
126    #[inline]
127    pub fn position(&self) -> i16 {
128        self.raw() as i16
129    }
130
131    /// Reset the encoder position to zero.
132    #[inline]
133    pub fn reset(&mut self) {
134        self.tim.cnt.write(|w| unsafe { w.bits(0) });
135    }
136}