omnitiles/motors/
lift_motor.rs

1// SPDX-License-Identifier: MIT
2// © 2025–2026 Christopher Liu
3
4//! Linear actuator interface built on top of `SpiMotor`.
5//!
6//! This module converts encoder ticks into physical height and provides target-based movement
7//! helpers, which can be used in a higher-level controller like a PID loop.
8
9use crate::drivers::Fit0185;
10
11/// Lift motor abstraction layered on top of a `SpiMotor`.
12///
13/// Geometry parameters:
14/// - `mm_per_rev`: mechanical displacement per output shaft revolution
15/// - `counts_per_rev`: inherited automatically from underlying motor
16pub struct LiftMotor<
17    const CS_P: char,
18    const CS_N: u8,
19    const IN1_P: char,
20    const IN1_N: u8,
21    const IN2_P: char,
22    const IN2_N: u8,
23    const SLP_P: char,
24    const SLP_N: u8,
25    const DIS_P: char,
26    const DIS_N: u8,
27> {
28    motor: Fit0185<CS_P, CS_N, IN1_P, IN1_N, IN2_P, IN2_N, SLP_P, SLP_N, DIS_P, DIS_N>,
29    mm_per_rev: f32,
30}
31
32impl<
33        const CS_P: char,
34        const CS_N: u8,
35        const IN1_P: char,
36        const IN1_N: u8,
37        const IN2_P: char,
38        const IN2_N: u8,
39        const SLP_P: char,
40        const SLP_N: u8,
41        const DIS_P: char,
42        const DIS_N: u8,
43    > LiftMotor<CS_P, CS_N, IN1_P, IN1_N, IN2_P, IN2_N, SLP_P, SLP_N, DIS_P, DIS_N>
44{
45    /// Create a new `LiftMotor` wrapper.
46    ///
47    /// `mm_per_rev` — how many millimeters the actuator moves per full revolution.
48    pub fn new(
49        motor: Fit0185<CS_P, CS_N, IN1_P, IN1_N, IN2_P, IN2_N, SLP_P, SLP_N, DIS_P, DIS_N>,
50        mm_per_rev: f32,
51    ) -> Self {
52        Self { motor, mm_per_rev }
53    }
54
55    /// Convert encoder ticks to millimeters of extension.
56    pub fn height_mm(&self) -> f32 {
57        let revs = self.motor.position_revs();
58        revs * self.mm_per_rev
59    }
60
61    /// Convert from mm to encoder ticks.
62    fn ticks_for_mm(&self, mm: f32) -> i32 {
63        let revs = mm / self.mm_per_rev;
64        self.motor.ticks_for_revs(revs)
65    }
66
67    /// Compute the target tick position for an absolute height command.
68    pub fn target_ticks_for_height(&self, mm: f32) -> i32 {
69        let revs = mm / self.mm_per_rev;
70        self.motor.ticks_for_revs(revs)
71    }
72
73    /// Compute a relative tick movement for a delta height.
74    pub fn delta_ticks_for_height(&self, delta_mm: f32) -> i32 {
75        self.ticks_for_mm(delta_mm)
76    }
77
78    /// Move by a relative number of millimeters.
79    pub fn move_by_mm(&mut self, delta_mm: f32) -> i32 {
80        let ticks = self.delta_ticks_for_height(delta_mm);
81        self.motor.target_for_delta_ticks(ticks)
82    }
83
84    /// Move by a relative number of revolutions.
85    pub fn move_by_revs(&mut self, revs: f32) -> i32 {
86        let ticks = self.motor.ticks_for_revs(revs);
87        self.motor.target_for_delta_ticks(ticks)
88    }
89
90    /// Command the lift to go to a specific height (mm), returning a tick target.
91    pub fn go_to_height_mm(&mut self, mm: f32) -> i32 {
92        let target = self.target_ticks_for_height(mm);
93        target
94    }
95
96    /// Reset encoder reference (zero height).
97    pub fn zero(&mut self) {
98        self.motor.zero();
99    }
100
101    /// Expose the underlying `SpiMotor`.
102    pub fn inner_motor(
103        &mut self,
104    ) -> &mut Fit0185<CS_P, CS_N, IN1_P, IN1_N, IN2_P, IN2_N, SLP_P, SLP_N, DIS_P, DIS_N> {
105        &mut self.motor
106    }
107
108    /// Apply PID output to the underlying motor.
109    pub fn apply_pid_output(&mut self, u: f32) {
110        self.motor.apply_pid_output(u)
111    }
112}