TMC5160 Driver
API reference
Loading...
Searching...
No Matches
tmc51x0_motor_calc.hpp
Go to the documentation of this file.
1
6#pragma once
7#include "../tmc51x0_types.hpp"
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
19constexpr float TMC5130_VFS_MV = 320.0F;
21constexpr float TMC5130_RSENSE_OFFSET_MOHM = 20.0F;
22} // namespace MotorCalcConstants
23
55inline bool CalculateMotorCurrent(const MotorSpec& motor_spec, uint32_t sense_resistor_mohm, uint32_t supply_voltage_mv,
56 uint16_t run_current_ma, uint16_t hold_current_ma, uint8_t& irun, uint8_t& ihold,
57 uint16_t& global_scaler, uint8_t chip_version = ChipVersion::TMC5160) noexcept {
58 // Validate inputs
59 if (sense_resistor_mohm == 0 || supply_voltage_mv == 0) {
60 return false;
61 }
62
63 if (chip_version == ChipVersion::TMC5130) {
64 if (run_current_ma == 0) {
65 run_current_ma = motor_spec.rated_current_ma;
66 }
67 if (run_current_ma == 0) {
68 return false;
69 }
70 if (hold_current_ma == 0) {
71 hold_current_ma = static_cast<uint16_t>(static_cast<float>(run_current_ma) * 0.3F);
72 }
73 // I_RMS_max (CS = 31) in mA: VFS / (RSENSE + 20 mOhm) / sqrt(2)
74 const float i_max_ma = (MotorCalcConstants::TMC5130_VFS_MV /
75 (static_cast<float>(sense_resistor_mohm) + MotorCalcConstants::TMC5130_RSENSE_OFFSET_MOHM)) /
77 if (static_cast<float>(run_current_ma) > i_max_ma * 1.1F) {
78 return false;
79 }
80 // CS = I * 32 / I_max - 1, rounded down so the coil current never exceeds the request.
81 auto cs_for = [i_max_ma](uint16_t ma) -> uint8_t {
82 const float cs = (static_cast<float>(ma) * 32.0F / i_max_ma) - 1.0F;
83 if (cs <= 0.0F) {
84 return 0;
85 }
86 return static_cast<uint8_t>(std::min(31.0F, std::floor(cs + 1e-3F)));
87 };
88 irun = cs_for(run_current_ma);
89 ihold = cs_for(hold_current_ma);
90 if (ihold >= irun) {
91 ihold = (irun > 0) ? static_cast<uint8_t>(irun - 1) : 0;
92 }
93 global_scaler = 256;
94 return true;
95 }
96
97 // Use motor rated current if run_current_ma is 0
98 if (run_current_ma == 0) {
99 run_current_ma = motor_spec.rated_current_ma;
100 }
101 if (run_current_ma == 0) {
102 return false; // No current specified
103 }
104
105 // Default hold current to 30% of run current if not specified
106 if (hold_current_ma == 0) {
107 float hold_current_float = run_current_ma * 0.3F;
108 hold_current_ma = static_cast<uint16_t>(hold_current_float);
109 }
110
111 // Use shared constants
112 constexpr float VFS_MV = MotorCalcConstants::VFS_MV;
113 constexpr float SQRT2 = MotorCalcConstants::SQRT2;
114
115 // Calculate maximum possible RMS current at full scale (GLOBAL_SCALER=256, CS=31)
116 // I_RMS_max (A) = (256/256) * ((31+1)/32) * (VFS_V/RSENSE_Ω) * (1/√2)
117 // = 1.0 * 1.0 * (VFS_V/RSENSE_Ω) * (1/√2)
118 // Converting from mV/mΩ to A: (VFS_mV/1000) / (RSENSE_mΩ/1000) = VFS_mV/RSENSE_mΩ (already in A)
119 // Then convert to mA: multiply by 1000
120 float i_rms_max_ma = ((VFS_MV / static_cast<float>(sense_resistor_mohm)) / SQRT2) * 1000.0F;
121
122 // Check if desired current exceeds maximum possible
123 if (static_cast<float>(run_current_ma) > i_rms_max_ma * 1.1F) { // Allow 10% tolerance
124 return false; // Desired current too high for sense resistor
125 }
126
127 // Strategy: Use IRUN in optimal range (16-31) and fine-tune with GLOBAL_SCALER
128 // Start with IRUN=31 (maximum CS) and calculate required GLOBAL_SCALER
129 // Then adjust IRUN down if GLOBAL_SCALER would be too low
130
131 // Calculate required GLOBAL_SCALER for IRUN=31.
132 //
133 // Datasheet equation (expressed in mA):
134 // I_RMS_ma = (GLOBAL_SCALER/256) * ((CS+1)/32) * I_RMS_MAX_ma
135 // where:
136 // I_RMS_MAX_ma = (VFS/RSENSE) * (1/√2) * 1000
137 //
138 // For CS=31: ((CS+1)/32)=1, so:
139 // GLOBAL_SCALER = I_RMS_ma * 256 / I_RMS_MAX_ma
140 //
141 // NOTE: We intentionally use i_rms_max_ma here to keep units consistent (mA).
142 float global_scaler_float =
143 (static_cast<float>(run_current_ma) * 256.0F) / std::max(i_rms_max_ma, 1.0F);
144
145 // Constrain GLOBAL_SCALER to valid range (32-256, where 0 = 256)
146 auto calculated_scaler = static_cast<uint16_t>(std::round(global_scaler_float));
147 calculated_scaler = std::max<uint16_t>(calculated_scaler, 32);
148 calculated_scaler = std::min<uint16_t>(calculated_scaler, 256);
149
150 // If GLOBAL_SCALER is at maximum (256), we can reduce IRUN for better precision
151 // Try to find optimal IRUN in range 16-31
152 uint8_t optimal_irun = 31;
153 uint16_t optimal_scaler = calculated_scaler;
154
155 if (calculated_scaler >= 200) {
156 // Try reducing IRUN to get GLOBAL_SCALER in better range (128-200)
157 for (uint8_t test_irun = 30; test_irun >= 16; --test_irun) {
158 // GLOBAL_SCALER = I_RMS_ma * 256 * 32 / ((CS+1) * I_RMS_MAX_ma)
159 float test_scaler_float =
160 (static_cast<float>(run_current_ma) * 256.0F * 32.0F) /
161 (static_cast<float>(test_irun + 1) * std::max(i_rms_max_ma, 1.0F));
162 auto test_scaler = static_cast<uint16_t>(std::round(test_scaler_float));
163
164 if (test_scaler >= 32 && test_scaler <= 200) {
165 optimal_irun = test_irun;
166 optimal_scaler = test_scaler;
167 break;
168 }
169 }
170 }
171
172 // Ensure IRUN meets minimum for automatic tuning (IRUN ≥ 8)
173 if (optimal_irun < 8) {
174 optimal_irun = 8;
175 // Recalculate scaler for IRUN=8
176 float scaler_float =
177 (static_cast<float>(run_current_ma) * 256.0F * 32.0F) / (9.0F * std::max(i_rms_max_ma, 1.0F));
178 optimal_scaler = static_cast<uint16_t>(std::round(scaler_float));
179 optimal_scaler = std::max<uint16_t>(optimal_scaler, 32);
180 optimal_scaler = std::min<uint16_t>(optimal_scaler, 256);
181 }
182
183 // Calculate IHOLD using same method
184 // Calculate required IHOLD (use same scaler as IRUN)
185 // I_RMS_ma = (GLOBAL_SCALER/256) * ((IHOLD+1)/32) * I_RMS_MAX_ma
186 // Rearranged:
187 // IHOLD = (I_RMS_ma * 256 * 32) / (GLOBAL_SCALER * I_RMS_MAX_ma) - 1
188 float ihold_float =
189 ((static_cast<float>(hold_current_ma) * 256.0F * 32.0F) /
190 (static_cast<float>(optimal_scaler) * std::max(i_rms_max_ma, 1.0F))) -
191 1.0F;
192
193 auto calculated_ihold = static_cast<uint8_t>(std::round(ihold_float));
194
195 // Constrain IHOLD to valid range (0-31) and ensure it's less than IRUN
196 calculated_ihold = std::min<uint8_t>(calculated_ihold, 31);
197 if (calculated_ihold >= optimal_irun) {
198 calculated_ihold = (optimal_irun > 0) ? (optimal_irun - 1) : 0;
199 }
200
201 irun = optimal_irun;
202 ihold = calculated_ihold;
203 global_scaler = optimal_scaler;
204
205 return true;
206}
207
225inline uint16_t CalculateStealthChopLowerLimit(const MotorSpec& motor_spec, uint32_t supply_voltage_mv, uint8_t tbl,
226 uint8_t pwm_freq, uint32_t f_clk = 12000000U) noexcept {
227 if (motor_spec.winding_resistance_mohm == 0) {
228 return 0; // Cannot calculate without resistance
229 }
230
231 // Blank time in clock cycles based on TBL setting
232 constexpr std::array<uint8_t, 4> blank_times = {16, 24, 36, 54};
233 const uint8_t tbl_index = std::min(tbl, static_cast<uint8_t>(3));
234 uint8_t t_blank = blank_times[tbl_index];
235
236 // PWM frequency divider based on PWM_FREQ setting
237 constexpr std::array<uint32_t, 4> pwm_divisors = {1024, 683, 512, 410};
238 const uint8_t pwm_freq_index = std::min(pwm_freq, static_cast<uint8_t>(3));
239 uint32_t pwm_divisor = pwm_divisors[pwm_freq_index];
240
241 // Calculate PWM frequency: f_PWM = 2 / (pwm_divisor * t_CLK)
242 // Actually: f_PWM = 2 / (pwm_divisor * (1/f_CLK)) = 2 * f_CLK / pwm_divisor
243 float f_pwm = (2.0F * static_cast<float>(f_clk)) / static_cast<float>(pwm_divisor);
244
245 // Convert to Hz: f_PWM = 2 / (pwm_divisor * t_CLK) where t_CLK = 1/f_CLK
246 // So: f_PWM = 2 * f_CLK / pwm_divisor (already calculated above)
247
248 // Calculate lower limit: I (mA) = t_BLANK * f_PWM * V_M (mV) / R_COIL (mΩ)
249 // t_BLANK is in clock cycles, so we need: t_BLANK / f_CLK (time in seconds)
250 float t_blank_sec = static_cast<float>(t_blank) / static_cast<float>(f_clk);
251 float i_lower_ma = (t_blank_sec * f_pwm * static_cast<float>(supply_voltage_mv)) /
252 static_cast<float>(motor_spec.winding_resistance_mohm);
253
254 return static_cast<uint16_t>(i_lower_ma);
255}
256
263inline uint16_t CalculateMaxCurrentForSenseResistor(uint32_t sense_resistor_mohm) noexcept {
264 if (sense_resistor_mohm == 0) {
265 return 0;
266 }
267
268 // Use shared constants
269 constexpr float VFS_MV = MotorCalcConstants::VFS_MV;
270 constexpr float SQRT2 = MotorCalcConstants::SQRT2;
271
272 // Calculate maximum RMS current directly in milliamps
273 // Converting from mV/mΩ to A: (VFS_mV/1000) / (RSENSE_mΩ/1000) = VFS_mV/RSENSE_mΩ (already in A)
274 // Then convert to mA: multiply by 1000
275 float i_rms_max_ma = ((VFS_MV / static_cast<float>(sense_resistor_mohm)) / SQRT2) * 1000.0F;
276
277 return static_cast<uint16_t>(i_rms_max_ma);
278}
279
295inline uint8_t CalculateS2VSLevel(uint16_t voltage_mv) noexcept {
296 if (voltage_mv == 0) {
297 return 6; // Default recommended value
298 }
299
300 // Constrain to valid range
301 if (voltage_mv < 400 || voltage_mv > 2000) {
302 return 0; // Invalid
303 }
304
305 // Datasheet typical values for interpolation
306 // S2VS_LEVEL=6: 625mV, S2VS_LEVEL=15: 1560mV
307 // Linear relationship: level = 6 + (voltage - 625) * (15-6) / (1560-625)
308 // Simplified: level = 6 + (voltage - 625) * 9 / 935
309
310 if (voltage_mv <= 625) {
311 // Below or at level 6 threshold - use linear interpolation from level 4 (approx 400mV) to level 6
312 // Level 4-6 range: approximate 400-625mV
313 if (voltage_mv < 400) {
314 return 4; // Minimum
315 }
316 float level = 4.0F + ((static_cast<float>(voltage_mv - 400) / 225.0F) * 2.0F); // 4 to 6
317 return static_cast<uint8_t>(std::round(level));
318 }
319 // Above level 6 threshold - interpolate from level 6 to level 15
320 float level = 6.0F + ((static_cast<float>(voltage_mv - 625) / 935.0F) * 9.0F); // 6 to 15
321 auto calculated = static_cast<uint8_t>(std::round(level));
322 return std::min(static_cast<uint8_t>(15), std::max(static_cast<uint8_t>(4), calculated));
323}
324
342inline uint8_t CalculateS2GLevel(uint16_t voltage_mv, uint32_t supply_voltage_mv = 0) noexcept {
343 if (voltage_mv == 0) {
344 return 6; // Default recommended value
345 }
346
347 // Constrain to valid range
348 if (voltage_mv < 400 || voltage_mv > 2000) {
349 return 0; // Invalid
350 }
351
352 // For VS>52V, enforce minimum 1200mV (S2G_LEVEL=12) to prevent false triggers
353 if (supply_voltage_mv > 52000 && voltage_mv < 1200) {
354 voltage_mv = 1200; // Enforce minimum
355 }
356
357 // Datasheet typical values for interpolation
358 // S2G_LEVEL=6: 625mV, S2G_LEVEL=15: 1560mV (VS<52V) or 850mV (VS<55V)
359 // Use VS<52V values for interpolation (more conservative)
360
361 if (voltage_mv <= 625) {
362 // Below or at level 6 threshold - use linear interpolation from level 2 (approx 400mV) to level 6
363 if (voltage_mv < 400) {
364 return 2; // Minimum
365 }
366 float level = 2.0F + ((static_cast<float>(voltage_mv - 400) / 225.0F) * 4.0F); // 2 to 6
367 return static_cast<uint8_t>(std::round(level));
368 }
369 // Above level 6 threshold - interpolate from level 6 to level 15
370 float level = 6.0F + ((static_cast<float>(voltage_mv - 625) / 935.0F) * 9.0F); // 6 to 15
371 auto calculated = static_cast<uint8_t>(std::round(level));
372 return std::min(static_cast<uint8_t>(15), std::max(static_cast<uint8_t>(2), calculated));
373}
374
389inline uint8_t CalculateShortDelay(uint8_t delay_us_x10) noexcept {
390 if (delay_us_x10 == 0) {
391 return 0; // Default recommended value (0.85µs = shortdelay=0)
392 }
393
394 // Constrain to valid range (5-25 = 0.5-2.5µs)
395 if (delay_us_x10 < 5) {
396 delay_us_x10 = 5; // Minimum 0.5µs
397 } else if (delay_us_x10 > 25) {
398 delay_us_x10 = 25; // Maximum 2.5µs
399 }
400
401 // Threshold at ~1.0µs (10 in 0.1µs units)
402 // Below 1.0µs: use shortdelay=0 (normal)
403 // At or above 1.0µs: use shortdelay=1 (high delay)
404 return (delay_us_x10 >= 10) ? 1 : 0;
405}
406
407} // namespace tmc51x0
constexpr uint8_t TMC5160
TMC5160 chip version.
Definition tmc51x0_types.hpp:26
constexpr uint8_t TMC5130
TMC5130 chip version.
Definition tmc51x0_types.hpp:25
constexpr float TMC5130_VFS_MV
TMC5130 full-scale sense voltage with CHOPCONF.vsense = 0 (TMC5130A datasheet, "Selecting sense resis...
Definition tmc51x0_motor_calc.hpp:19
constexpr float TMC5130_RSENSE_OFFSET_MOHM
TMC5130 internal resistance added to RSENSE in its current formula (TMC5130A datasheet,...
Definition tmc51x0_motor_calc.hpp:21
constexpr float VFS_MV
Definition tmc51x0_motor_calc.hpp:16
constexpr float SQRT2
Definition tmc51x0_motor_calc.hpp:17
Definition tmc51x0_config_builder.hpp:75
uint16_t CalculateMaxCurrentForSenseResistor(uint32_t sense_resistor_mohm) noexcept
Calculate maximum RMS current for a given sense resistor.
Definition tmc51x0_motor_calc.hpp:263
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:342
uint8_t CalculateShortDelay(uint8_t delay_us_x10) noexcept
Calculate shortdelay register bit from detection delay time.
Definition tmc51x0_motor_calc.hpp:389
uint8_t CalculateS2VSLevel(uint16_t voltage_mv) noexcept
Calculate S2VS_LEVEL register value from voltage threshold.
Definition tmc51x0_motor_calc.hpp:295
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, uint8_t chip_version=ChipVersion::TMC5160) noexcept
Calculate motor current settings from physical parameters.
Definition tmc51x0_motor_calc.hpp:55
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:225
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