Skip to main content

blackbox_rs/
knobs.rs

1//! 4 endless rotary encoders, read through ADC1 (README §knobs).
2//!
3//! Each Alps endless encoder has two analog wipers 90° out of phase; `atan2` of the two
4//! recovers an absolute angle. 16-bit + 8× hardware averaging + a long sample window settle
5//! the resistive wipers against SAI crosstalk.
6
7use embassy_stm32::adc::{Adc, AdcConfig, AnyAdcChannel, Averaging, Resolution, SampleTime};
8use embassy_stm32::peripherals::ADC1;
9
10/// Number of encoders.
11pub const COUNT: usize = 4;
12
13/// A named encoder. Discriminant = wiper-pair position in [`Knobs::new`].
14#[derive(Clone, Copy, PartialEq, Eq, defmt::Format)]
15pub enum Knob {
16    /// Top-left
17    Tl,
18    /// Top-right
19    Tr,
20    /// Bottom-left
21    Bl,
22    /// Bottom-right
23    Br,
24}
25
26impl Knob {
27    pub const ALL: [Knob; COUNT] = [Knob::Tl, Knob::Tr, Knob::Bl, Knob::Br];
28
29    pub const fn index(self) -> usize {
30        self as usize
31    }
32
33    pub const fn label(self) -> &'static str {
34        match self {
35            Knob::Tl => "TL",
36            Knob::Tr => "TR",
37            Knob::Bl => "BL",
38            Knob::Br => "BR",
39        }
40    }
41}
42
43/// One encoder reading: the two raw wipers and the recovered angle.
44#[derive(Clone, Copy)]
45pub struct Reading {
46    pub a: u16,
47    pub b: u16,
48}
49
50impl Reading {
51    /// Absolute angle in degrees from the two 90°-out-of-phase wipers.
52    pub fn angle_deg(&self) -> i32 {
53        const MID: f32 = 32768.0; // 16-bit midscale
54        let rad = libm::atan2f(self.a as f32 - MID, self.b as f32 - MID);
55        (rad * 180.0 / core::f32::consts::PI) as i32
56    }
57}
58
59/// Long sample window settling the resistive wipers against SAI crosstalk.
60const SAMPLE_TIME: SampleTime = SampleTime::CYCLES810_5;
61
62/// The board's 4 encoders on ADC1. Construct via [`crate::init`].
63pub struct Knobs {
64    adc: Adc<'static, ADC1>,
65    /// Wiper channels, paired per encoder in [`Knob::ALL`] order: [a, b, a, b, ...].
66    wipers: [AnyAdcChannel<'static, ADC1>; COUNT * 2],
67}
68
69impl Knobs {
70    pub fn new(
71        adc1: embassy_stm32::Peri<'static, ADC1>,
72        wipers: [AnyAdcChannel<'static, ADC1>; COUNT * 2],
73    ) -> Self {
74        // 16-bit + 8× hardware averaging, applied through the v0.6 AdcConfig.
75        let adc = Adc::new_with_config(
76            adc1,
77            AdcConfig {
78                resolution: Some(Resolution::BITS16),
79                averaging: Some(Averaging::Samples8),
80            },
81        );
82        Self { adc, wipers }
83    }
84
85    /// Sample one encoder.
86    pub fn read(&mut self, k: Knob) -> Reading {
87        let i = k.index() * 2;
88        let a = self.adc.blocking_read(&mut self.wipers[i], SAMPLE_TIME);
89        let b = self.adc.blocking_read(&mut self.wipers[i + 1], SAMPLE_TIME);
90        Reading { a, b }
91    }
92
93    /// Sample all 4 encoders, indexed by [`Knob::index`].
94    pub fn read_all(&mut self) -> [Reading; COUNT] {
95        core::array::from_fn(|i| {
96            let a = self.adc.blocking_read(&mut self.wipers[i * 2], SAMPLE_TIME);
97            let b = self.adc.blocking_read(&mut self.wipers[i * 2 + 1], SAMPLE_TIME);
98            Reading { a, b }
99        })
100    }
101}