tmc2209_uart/
util.rs

1//! Utility functions for TMC2209 calculations.
2//!
3//! This module provides helper functions for common calculations like
4//! RMS current, velocity conversions, etc.
5
6/// Default sense resistor value in ohms (common value).
7pub const DEFAULT_RSENSE: f32 = 0.11;
8
9/// Internal voltage reference for current sensing (in volts).
10pub const VREF: f32 = 0.325;
11
12/// Round a f32 value to the nearest integer (no_std compatible).
13#[inline]
14fn round_f32(x: f32) -> f32 {
15    // Simple rounding: add 0.5 and truncate for positive, subtract 0.5 for negative
16    if x >= 0.0 {
17        (x + 0.5) as i32 as f32
18    } else {
19        (x - 0.5) as i32 as f32
20    }
21}
22
23/// Calculate the CS (current scale) value for a given RMS current.
24///
25/// # Arguments
26///
27/// * `rms_current_ma` - Desired RMS motor current in milliamps
28/// * `rsense` - Sense resistor value in ohms
29/// * `vsense` - VSENSE bit setting (true = high sensitivity, false = low)
30///
31/// # Returns
32///
33/// The CS value (0-31) to use in IRUN or IHOLD, or None if current is too high.
34///
35/// # Formula
36///
37/// For VSENSE=0 (low sensitivity):
38///   I_RMS = (CS + 1) / 32 * V_FS / (sqrt(2) * R_SENSE)
39///   where V_FS = 0.325V
40///
41/// For VSENSE=1 (high sensitivity):
42///   V_FS = 0.180V
43///
44/// Solving for CS:
45///   CS = (I_RMS * sqrt(2) * R_SENSE * 32 / V_FS) - 1
46pub fn current_to_cs(rms_current_ma: u16, rsense: f32, vsense: bool) -> Option<u8> {
47    let rms_current = rms_current_ma as f32 / 1000.0;
48    let vfs = if vsense { 0.180 } else { 0.325 };
49
50    // sqrt(2) ≈ 1.41421356
51    let sqrt2 = 1.41421356f32;
52    let cs_float = (rms_current * sqrt2 * rsense * 32.0 / vfs) - 1.0;
53
54    if cs_float < 0.0 {
55        Some(0)
56    } else if cs_float > 31.0 {
57        None // Current too high for this setting
58    } else {
59        Some(round_f32(cs_float) as u8)
60    }
61}
62
63/// Calculate the RMS current for a given CS value.
64///
65/// # Arguments
66///
67/// * `cs` - Current scale value (0-31)
68/// * `rsense` - Sense resistor value in ohms
69/// * `vsense` - VSENSE bit setting (true = high sensitivity, false = low)
70///
71/// # Returns
72///
73/// The RMS current in milliamps.
74pub fn cs_to_current(cs: u8, rsense: f32, vsense: bool) -> u16 {
75    let vfs = if vsense { 0.180 } else { 0.325 };
76    let sqrt2 = 1.41421356f32;
77
78    let cs = (cs.min(31) + 1) as f32;
79    let rms_current = cs / 32.0 * vfs / (sqrt2 * rsense);
80
81    round_f32(rms_current * 1000.0) as u16
82}
83
84/// Determine optimal VSENSE setting for a given RMS current.
85///
86/// Returns true if high sensitivity (VSENSE=1) should be used.
87/// High sensitivity is preferred for lower currents for better precision.
88///
89/// # Arguments
90///
91/// * `rms_current_ma` - Desired RMS current in milliamps
92/// * `rsense` - Sense resistor value in ohms
93pub fn optimal_vsense(rms_current_ma: u16, rsense: f32) -> bool {
94    // Calculate max current for VSENSE=1 (high sensitivity)
95    let max_current_vsense1 = cs_to_current(31, rsense, true);
96
97    // Use high sensitivity if desired current is within range
98    rms_current_ma <= max_current_vsense1
99}
100
101/// Calculate CS and VSENSE for a target RMS current.
102///
103/// This function automatically selects the optimal VSENSE setting
104/// and returns the corresponding CS value.
105///
106/// # Arguments
107///
108/// * `rms_current_ma` - Desired RMS current in milliamps
109/// * `rsense` - Sense resistor value in ohms
110///
111/// # Returns
112///
113/// A tuple of (CS, VSENSE), or None if current is too high.
114pub fn calculate_current_settings(rms_current_ma: u16, rsense: f32) -> Option<(u8, bool)> {
115    // Try high sensitivity first (better for lower currents)
116    let vsense = optimal_vsense(rms_current_ma, rsense);
117    let cs = current_to_cs(rms_current_ma, rsense, vsense)?;
118
119    Some((cs, vsense))
120}
121
122/// Convert velocity in steps/second to VACTUAL register value.
123///
124/// # Arguments
125///
126/// * `steps_per_sec` - Velocity in full steps per second
127/// * `microsteps` - Microstep resolution (1, 2, 4, 8, 16, 32, 64, 128, or 256)
128/// * `fclk` - Internal clock frequency in Hz (typically 12 MHz)
129///
130/// # Returns
131///
132/// The VACTUAL register value.
133///
134/// # Formula
135///
136/// VACTUAL = velocity * 2^23 / fCLK
137/// where velocity is in microsteps/second
138pub fn velocity_to_vactual(steps_per_sec: f32, microsteps: u16, fclk: u32) -> i32 {
139    let microsteps_per_sec = steps_per_sec * microsteps as f32;
140    let vactual = microsteps_per_sec * 8388608.0 / fclk as f32; // 2^23 = 8388608
141    round_f32(vactual) as i32
142}
143
144/// Convert TSTEP register value to velocity in steps/second.
145///
146/// # Arguments
147///
148/// * `tstep` - TSTEP register value
149/// * `microsteps` - Microstep resolution
150/// * `fclk` - Internal clock frequency in Hz (typically 12 MHz)
151///
152/// # Returns
153///
154/// Velocity in full steps per second, or None if tstep is 0.
155pub fn tstep_to_velocity(tstep: u32, microsteps: u16, fclk: u32) -> Option<f32> {
156    if tstep == 0 {
157        return None;
158    }
159
160    // TSTEP is the time between microsteps in clock cycles
161    let microsteps_per_sec = fclk as f32 / tstep as f32;
162    let steps_per_sec = microsteps_per_sec / microsteps as f32;
163
164    Some(steps_per_sec)
165}
166
167/// Calculate TPWMTHRS for a given velocity threshold.
168///
169/// TPWMTHRS sets the upper velocity limit for StealthChop.
170/// Above this velocity, the driver switches to SpreadCycle.
171///
172/// # Arguments
173///
174/// * `steps_per_sec` - Velocity threshold in full steps per second
175/// * `microsteps` - Microstep resolution
176/// * `fclk` - Internal clock frequency in Hz
177///
178/// # Returns
179///
180/// The TPWMTHRS register value.
181pub fn velocity_to_tpwmthrs(steps_per_sec: f32, microsteps: u16, fclk: u32) -> u32 {
182    if steps_per_sec <= 0.0 {
183        return 0xFFFFF; // Maximum value (always StealthChop)
184    }
185
186    let microsteps_per_sec = steps_per_sec * microsteps as f32;
187    let tstep = fclk as f32 / microsteps_per_sec;
188
189    (tstep as u32).min(0xFFFFF)
190}
191
192/// Default TMC2209 internal clock frequency (12 MHz).
193pub const DEFAULT_FCLK: u32 = 12_000_000;
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_current_to_cs() {
201        // With 0.11 ohm sense resistor, VSENSE=0
202        // Max current ≈ 2.1A RMS
203        let cs = current_to_cs(1000, 0.11, false);
204        assert!(cs.is_some());
205
206        // Very high current should return None
207        let cs = current_to_cs(5000, 0.11, false);
208        assert!(cs.is_none());
209    }
210
211    #[test]
212    fn test_cs_to_current() {
213        // CS=31 with 0.11 ohm, VSENSE=0 should give max current
214        let current = cs_to_current(31, 0.11, false);
215        assert!(current > 2000); // Should be around 2.1A
216    }
217
218    #[test]
219    fn test_velocity_conversion() {
220        // 100 steps/sec with 256 microsteps at 12MHz
221        let vactual = velocity_to_vactual(100.0, 256, 12_000_000);
222        assert!(vactual > 0);
223
224        // Convert back from TSTEP
225        let tstep = 12_000_000 / (100 * 256); // Approximate
226        let velocity = tstep_to_velocity(tstep as u32, 256, 12_000_000);
227        assert!(velocity.is_some());
228    }
229}