HF-TMC51x0 Driver (TMC5130 & TMC5160) 0.1.0-dev
Hardware Agnostic C++ Driver for the TMC51x0 (TMC5130 & TMC5160)
Loading...
Searching...
No Matches
tmc51x0_motor_calc.hpp
Go to the documentation of this file.
1
6#pragma once
8#include <algorithm>
9#include <array>
10#include <cmath>
11
12namespace tmc51x0 {
13
14// Datasheet constants (shared across calculation functions)
15namespace MotorCalcConstants {
16constexpr float VFS_MV = 325.0F; // Typical full-scale voltage (mV) = 0.325V
17constexpr float SQRT2 = 1.41421356237F; // √2
18} // namespace MotorCalcConstants
19
47inline bool CalculateMotorCurrent(const MotorSpec& motor_spec, uint32_t sense_resistor_mohm, uint32_t supply_voltage_mv,
48 uint16_t run_current_ma, uint16_t hold_current_ma, uint8_t& irun, uint8_t& ihold,
49 uint16_t& global_scaler) noexcept {
50 // Validate inputs
51 if (sense_resistor_mohm == 0 || supply_voltage_mv == 0) {
52 return false;
53 }
54
55 // Use motor rated current if run_current_ma is 0
56 if (run_current_ma == 0) {
57 run_current_ma = motor_spec.rated_current_ma;
58 }
59 if (run_current_ma == 0) {
60 return false; // No current specified
61 }
62
63 // Default hold current to 30% of run current if not specified
64 if (hold_current_ma == 0) {
65 float hold_current_float = run_current_ma * 0.3F;
66 hold_current_ma = static_cast<uint16_t>(hold_current_float);
67 }
68
69 // Use shared constants
70 constexpr float VFS_MV = MotorCalcConstants::VFS_MV;
71 constexpr float SQRT2 = MotorCalcConstants::SQRT2;
72
73 // Calculate maximum possible RMS current at full scale (GLOBAL_SCALER=256, CS=31)
74 // I_RMS_max (A) = (256/256) * ((31+1)/32) * (VFS_V/RSENSE_Ω) * (1/√2)
75 // = 1.0 * 1.0 * (VFS_V/RSENSE_Ω) * (1/√2)
76 // Converting from mV/mΩ to A: (VFS_mV/1000) / (RSENSE_mΩ/1000) = VFS_mV/RSENSE_mΩ (already in A)
77 // Then convert to mA: multiply by 1000
78 float i_rms_max_ma = ((VFS_MV / static_cast<float>(sense_resistor_mohm)) / SQRT2) * 1000.0F;
79
80 // Check if desired current exceeds maximum possible
81 if (static_cast<float>(run_current_ma) > i_rms_max_ma * 1.1F) { // Allow 10% tolerance
82 return false; // Desired current too high for sense resistor
83 }
84
85 // Strategy: Use IRUN in optimal range (16-31) and fine-tune with GLOBAL_SCALER
86 // Start with IRUN=31 (maximum CS) and calculate required GLOBAL_SCALER
87 // Then adjust IRUN down if GLOBAL_SCALER would be too low
88
89 // Calculate required GLOBAL_SCALER for IRUN=31.
90 //
91 // Datasheet equation (expressed in mA):
92 // I_RMS_ma = (GLOBAL_SCALER/256) * ((CS+1)/32) * I_RMS_MAX_ma
93 // where:
94 // I_RMS_MAX_ma = (VFS/RSENSE) * (1/√2) * 1000
95 //
96 // For CS=31: ((CS+1)/32)=1, so:
97 // GLOBAL_SCALER = I_RMS_ma * 256 / I_RMS_MAX_ma
98 //
99 // NOTE: We intentionally use i_rms_max_ma here to keep units consistent (mA).
100 float global_scaler_float =
101 (static_cast<float>(run_current_ma) * 256.0F) / std::max(i_rms_max_ma, 1.0F);
102
103 // Constrain GLOBAL_SCALER to valid range (32-256, where 0 = 256)
104 auto calculated_scaler = static_cast<uint16_t>(std::round(global_scaler_float));
105 calculated_scaler = std::max<uint16_t>(calculated_scaler, 32);
106 calculated_scaler = std::min<uint16_t>(calculated_scaler, 256);
107
108 // If GLOBAL_SCALER is at maximum (256), we can reduce IRUN for better precision
109 // Try to find optimal IRUN in range 16-31
110 uint8_t optimal_irun = 31;
111 uint16_t optimal_scaler = calculated_scaler;
112
113 if (calculated_scaler >= 200) {
114 // Try reducing IRUN to get GLOBAL_SCALER in better range (128-200)
115 for (uint8_t test_irun = 30; test_irun >= 16; --test_irun) {
116 // GLOBAL_SCALER = I_RMS_ma * 256 * 32 / ((CS+1) * I_RMS_MAX_ma)
117 float test_scaler_float =
118 (static_cast<float>(run_current_ma) * 256.0F * 32.0F) /
119 (static_cast<float>(test_irun + 1) * std::max(i_rms_max_ma, 1.0F));
120 auto test_scaler = static_cast<uint16_t>(std::round(test_scaler_float));
121
122 if (test_scaler >= 32 && test_scaler <= 200) {
123 optimal_irun = test_irun;
124 optimal_scaler = test_scaler;
125 break;
126 }
127 }
128 }
129
130 // Ensure IRUN meets minimum for automatic tuning (IRUN ≥ 8)
131 if (optimal_irun < 8) {
132 optimal_irun = 8;
133 // Recalculate scaler for IRUN=8
134 float scaler_float =
135 (static_cast<float>(run_current_ma) * 256.0F * 32.0F) / (9.0F * std::max(i_rms_max_ma, 1.0F));
136 optimal_scaler = static_cast<uint16_t>(std::round(scaler_float));
137 optimal_scaler = std::max<uint16_t>(optimal_scaler, 32);
138 optimal_scaler = std::min<uint16_t>(optimal_scaler, 256);
139 }
140
141 // Calculate IHOLD using same method
142 // Calculate required IHOLD (use same scaler as IRUN)
143 // I_RMS_ma = (GLOBAL_SCALER/256) * ((IHOLD+1)/32) * I_RMS_MAX_ma
144 // Rearranged:
145 // IHOLD = (I_RMS_ma * 256 * 32) / (GLOBAL_SCALER * I_RMS_MAX_ma) - 1
146 float ihold_float =
147 ((static_cast<float>(hold_current_ma) * 256.0F * 32.0F) /
148 (static_cast<float>(optimal_scaler) * std::max(i_rms_max_ma, 1.0F))) -
149 1.0F;
150
151 auto calculated_ihold = static_cast<uint8_t>(std::round(ihold_float));
152
153 // Constrain IHOLD to valid range (0-31) and ensure it's less than IRUN
154 calculated_ihold = std::min<uint8_t>(calculated_ihold, 31);
155 if (calculated_ihold >= optimal_irun) {
156 calculated_ihold = (optimal_irun > 0) ? (optimal_irun - 1) : 0;
157 }
158
159 irun = optimal_irun;
160 ihold = calculated_ihold;
161 global_scaler = optimal_scaler;
162
163 return true;
164}
165
183inline uint16_t CalculateStealthChopLowerLimit(const MotorSpec& motor_spec, uint32_t supply_voltage_mv, uint8_t tbl,
184 uint8_t pwm_freq, uint32_t f_clk = 12000000U) noexcept {
185 if (motor_spec.winding_resistance_mohm == 0) {
186 return 0; // Cannot calculate without resistance
187 }
188
189 // Blank time in clock cycles based on TBL setting
190 constexpr std::array<uint8_t, 4> blank_times = {16, 24, 36, 54};
191 const uint8_t tbl_index = std::min(tbl, static_cast<uint8_t>(3));
192 uint8_t t_blank = blank_times[tbl_index];
193
194 // PWM frequency divider based on PWM_FREQ setting
195 constexpr std::array<uint32_t, 4> pwm_divisors = {1024, 683, 512, 410};
196 const uint8_t pwm_freq_index = std::min(pwm_freq, static_cast<uint8_t>(3));
197 uint32_t pwm_divisor = pwm_divisors[pwm_freq_index];
198
199 // Calculate PWM frequency: f_PWM = 2 / (pwm_divisor * t_CLK)
200 // Actually: f_PWM = 2 / (pwm_divisor * (1/f_CLK)) = 2 * f_CLK / pwm_divisor
201 float f_pwm = (2.0F * static_cast<float>(f_clk)) / static_cast<float>(pwm_divisor);
202
203 // Convert to Hz: f_PWM = 2 / (pwm_divisor * t_CLK) where t_CLK = 1/f_CLK
204 // So: f_PWM = 2 * f_CLK / pwm_divisor (already calculated above)
205
206 // Calculate lower limit: I (mA) = t_BLANK * f_PWM * V_M (mV) / R_COIL (mΩ)
207 // t_BLANK is in clock cycles, so we need: t_BLANK / f_CLK (time in seconds)
208 float t_blank_sec = static_cast<float>(t_blank) / static_cast<float>(f_clk);
209 float i_lower_ma = (t_blank_sec * f_pwm * static_cast<float>(supply_voltage_mv)) /
210 static_cast<float>(motor_spec.winding_resistance_mohm);
211
212 return static_cast<uint16_t>(i_lower_ma);
213}
214
221inline uint16_t CalculateMaxCurrentForSenseResistor(uint32_t sense_resistor_mohm) noexcept {
222 if (sense_resistor_mohm == 0) {
223 return 0;
224 }
225
226 // Use shared constants
227 constexpr float VFS_MV = MotorCalcConstants::VFS_MV;
228 constexpr float SQRT2 = MotorCalcConstants::SQRT2;
229
230 // Calculate maximum RMS current directly in milliamps
231 // Converting from mV/mΩ to A: (VFS_mV/1000) / (RSENSE_mΩ/1000) = VFS_mV/RSENSE_mΩ (already in A)
232 // Then convert to mA: multiply by 1000
233 float i_rms_max_ma = ((VFS_MV / static_cast<float>(sense_resistor_mohm)) / SQRT2) * 1000.0F;
234
235 return static_cast<uint16_t>(i_rms_max_ma);
236}
237
253inline uint8_t CalculateS2VSLevel(uint16_t voltage_mv) noexcept {
254 if (voltage_mv == 0) {
255 return 6; // Default recommended value
256 }
257
258 // Constrain to valid range
259 if (voltage_mv < 400 || voltage_mv > 2000) {
260 return 0; // Invalid
261 }
262
263 // Datasheet typical values for interpolation
264 // S2VS_LEVEL=6: 625mV, S2VS_LEVEL=15: 1560mV
265 // Linear relationship: level = 6 + (voltage - 625) * (15-6) / (1560-625)
266 // Simplified: level = 6 + (voltage - 625) * 9 / 935
267
268 if (voltage_mv <= 625) {
269 // Below or at level 6 threshold - use linear interpolation from level 4 (approx 400mV) to level 6
270 // Level 4-6 range: approximate 400-625mV
271 if (voltage_mv < 400) {
272 return 4; // Minimum
273 }
274 float level = 4.0F + ((static_cast<float>(voltage_mv - 400) / 225.0F) * 2.0F); // 4 to 6
275 return static_cast<uint8_t>(std::round(level));
276 }
277 // Above level 6 threshold - interpolate from level 6 to level 15
278 float level = 6.0F + ((static_cast<float>(voltage_mv - 625) / 935.0F) * 9.0F); // 6 to 15
279 auto calculated = static_cast<uint8_t>(std::round(level));
280 return std::min(static_cast<uint8_t>(15), std::max(static_cast<uint8_t>(4), calculated));
281}
282
300inline uint8_t CalculateS2GLevel(uint16_t voltage_mv, uint32_t supply_voltage_mv = 0) noexcept {
301 if (voltage_mv == 0) {
302 return 6; // Default recommended value
303 }
304
305 // Constrain to valid range
306 if (voltage_mv < 400 || voltage_mv > 2000) {
307 return 0; // Invalid
308 }
309
310 // For VS>52V, enforce minimum 1200mV (S2G_LEVEL=12) to prevent false triggers
311 if (supply_voltage_mv > 52000 && voltage_mv < 1200) {
312 voltage_mv = 1200; // Enforce minimum
313 }
314
315 // Datasheet typical values for interpolation
316 // S2G_LEVEL=6: 625mV, S2G_LEVEL=15: 1560mV (VS<52V) or 850mV (VS<55V)
317 // Use VS<52V values for interpolation (more conservative)
318
319 if (voltage_mv <= 625) {
320 // Below or at level 6 threshold - use linear interpolation from level 2 (approx 400mV) to level 6
321 if (voltage_mv < 400) {
322 return 2; // Minimum
323 }
324 float level = 2.0F + ((static_cast<float>(voltage_mv - 400) / 225.0F) * 4.0F); // 2 to 6
325 return static_cast<uint8_t>(std::round(level));
326 }
327 // Above level 6 threshold - interpolate from level 6 to level 15
328 float level = 6.0F + ((static_cast<float>(voltage_mv - 625) / 935.0F) * 9.0F); // 6 to 15
329 auto calculated = static_cast<uint8_t>(std::round(level));
330 return std::min(static_cast<uint8_t>(15), std::max(static_cast<uint8_t>(2), calculated));
331}
332
347inline uint8_t CalculateShortDelay(uint8_t delay_us_x10) noexcept {
348 if (delay_us_x10 == 0) {
349 return 0; // Default recommended value (0.85µs = shortdelay=0)
350 }
351
352 // Constrain to valid range (5-25 = 0.5-2.5µs)
353 if (delay_us_x10 < 5) {
354 delay_us_x10 = 5; // Minimum 0.5µs
355 } else if (delay_us_x10 > 25) {
356 delay_us_x10 = 25; // Maximum 2.5µs
357 }
358
359 // Threshold at ~1.0µs (10 in 0.1µs units)
360 // Below 1.0µs: use shortdelay=0 (normal)
361 // At or above 1.0µs: use shortdelay=1 (high delay)
362 return (delay_us_x10 >= 10) ? 1 : 0;
363}
364
365} // namespace tmc51x0
constexpr float VFS_MV
Definition tmc51x0_motor_calc.hpp:16
constexpr float SQRT2
Definition tmc51x0_motor_calc.hpp:17
Definition tmc51x0_register_defs.cpp:10
uint16_t CalculateMaxCurrentForSenseResistor(uint32_t sense_resistor_mohm) noexcept
Calculate maximum RMS current for a given sense resistor.
Definition tmc51x0_motor_calc.hpp:221
uint8_t CalculateS2GLevel(uint16_t voltage_mv, uint32_t supply_voltage_mv=0) noexcept
Calculate S2G_LEVEL register value from voltage threshold.
Definition tmc51x0_motor_calc.hpp:300
uint8_t CalculateShortDelay(uint8_t delay_us_x10) noexcept
Calculate shortdelay register bit from detection delay time.
Definition tmc51x0_motor_calc.hpp:347
uint8_t CalculateS2VSLevel(uint16_t voltage_mv) noexcept
Calculate S2VS_LEVEL register value from voltage threshold.
Definition tmc51x0_motor_calc.hpp:253
bool CalculateMotorCurrent(const MotorSpec &motor_spec, uint32_t sense_resistor_mohm, uint32_t supply_voltage_mv, uint16_t run_current_ma, uint16_t hold_current_ma, uint8_t &irun, uint8_t &ihold, uint16_t &global_scaler) noexcept
Calculate motor current settings from physical parameters.
Definition tmc51x0_motor_calc.hpp:47
uint16_t CalculateStealthChopLowerLimit(const MotorSpec &motor_spec, uint32_t supply_voltage_mv, uint8_t tbl, uint8_t pwm_freq, uint32_t f_clk=12000000U) noexcept
Calculate StealthChop lower current limit.
Definition tmc51x0_motor_calc.hpp:183
Motor specification structure.
Definition tmc51x0_types.hpp:322
uint32_t winding_resistance_mohm
Winding resistance in milliohms (required for StealthChop lower limit calc)
Definition tmc51x0_types.hpp:332
Type definitions and enumerations for TMC51x0 stepper motor driver (TMC5130 & TMC5160)