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}