13#ifdef FATIGUE_MOTION_HEADER_INCLUDED
32#ifdef FATIGUE_MOTION_HEADER_INCLUDED
46 : driver_(driver), global_min_bound_(0.0f), global_max_bound_(0.0f), local_min_bound_(0.0f), local_max_bound_(0.0f),
47 home_position_(0.0f), bounded_(
false), amplitude_(1000.0F),
48 vmax_rpm_(60.0f), amax_rev_s2_(10.0f),
49 dwell_at_min_ms_(0), dwell_at_max_ms_(0), running_(
false), start_time_us_(0),
50 target_cycles_(0), current_cycles_(0), cycle_complete_(
false),
51 state_(MotionState::STOPPED), dwell_start_time_ms_(0),
52 estimated_frequency_hz_(0.0f),
53 driver_mutex_(driver_mutex) {
60void FatigueTestMotion::SetGlobalBounds(
float min_bound_degrees,
float max_bound_degrees) noexcept {
63 global_min_bound_ = min_bound_degrees;
64 global_max_bound_ = max_bound_degrees;
67 ESP_LOGI(
TAG_MOTION,
"Global bounds set: min=%.2f°, max=%.2f°", global_min_bound_, global_max_bound_);
72 if (local_min_bound_ != 0.0f || local_max_bound_ != 0.0f) {
74 ClipLocalBoundsToGlobal();
82 min_degrees = global_min_bound_;
83 max_degrees = global_max_bound_;
90 home_position_ = current_position_degrees;
91 global_min_bound_ = current_position_degrees - default_range_degrees / 2.0f;
92 global_max_bound_ = current_position_degrees + default_range_degrees / 2.0f;
100 ESP_LOGW(
TAG_MOTION,
"Unbounded mode: No mechanical stops found");
101 ESP_LOGI(
TAG_MOTION,
"Using current position as home: %.2f°", current_position_degrees);
102 ESP_LOGI(
TAG_MOTION,
"Default global range: [%.2f°, %.2f°]", global_min_bound_, global_max_bound_);
106 float edge_backoff_deg)
noexcept {
107 float min_deg, max_deg;
109 float global_min, global_max;
112 min_deg = min_degrees_from_center;
113 max_deg = max_degrees_from_center;
114 is_bounded = bounded_;
115 global_min = global_min_bound_;
116 global_max = global_max_bound_;
121 min_deg = std::max(min_deg, global_min);
122 max_deg = std::min(max_deg, global_max);
127 if (edge_backoff_deg > 0.0f) {
128 float backoff_min = min_deg + edge_backoff_deg;
129 float backoff_max = max_deg - edge_backoff_deg;
132 if (backoff_min < backoff_max) {
133 ESP_LOGI(
TAG_MOTION,
"Applying edge backoff: %.2f° (min: %.2f° -> %.2f°, max: %.2f° -> %.2f°)",
134 edge_backoff_deg, min_deg, backoff_min, max_deg, backoff_max);
135 min_deg = backoff_min;
136 max_deg = backoff_max;
138 ESP_LOGW(
TAG_MOTION,
"Edge backoff %.2f° too large for range [%.2f°, %.2f°] - using full range",
139 edge_backoff_deg, min_deg, max_deg);
145 local_min_bound_ = min_deg;
146 local_max_bound_ = max_deg;
147 home_position_ = (local_min_bound_ + local_max_bound_) / 2.0f;
148 amplitude_ = (local_max_bound_ - local_min_bound_) / 2.0f;
151 ESP_LOGI(
TAG_MOTION,
"Local bounds set: min=%.2f°, max=%.2f° (with %.2f° edge backoff)",
152 min_deg, max_deg, edge_backoff_deg);
157 RecalculateEstimatedFrequency();
164 min_degrees = local_min_bound_;
165 max_degrees = local_max_bound_;
177 if (clamped != vmax_rpm) {
178 ESP_LOGW(
TAG_MOTION,
"VMAX clamped: %.1f -> %.1f RPM (limits: %.1f-%.1f)",
184 RecalculateEstimatedFrequency();
190 ESP_LOGI(
TAG_MOTION,
"Max velocity set: %.1f RPM", clamped);
202 if (clamped != amax_rev_s2) {
203 ESP_LOGW(
TAG_MOTION,
"AMAX clamped: %.2f -> %.2f rev/s² (limits: %.2f-%.2f)",
208 amax_rev_s2_ = clamped;
209 RecalculateEstimatedFrequency();
216 ESP_LOGI(
TAG_MOTION,
"Acceleration set: %.2f rev/s²", clamped);
234 dwell_at_min_ms_ = dwell_at_min_ms;
235 dwell_at_max_ms_ = dwell_at_max_ms;
236 RecalculateEstimatedFrequency();
238 ESP_LOGI(
TAG_MOTION,
"Dwell times updated: min=%lu ms, max=%lu ms",
239 dwell_at_min_ms, dwell_at_max_ms);
245 dwell_at_min_ms = dwell_at_min_ms_;
246 dwell_at_max_ms = dwell_at_max_ms_;
252 target_cycles_ = cycles;
254 ESP_LOGI(
TAG_MOTION,
"Target cycles set: %lu (0 = infinite)", target_cycles_);
288 uint32_t current_cycles, target_cycles;
294 ESP_LOGE(
TAG_MOTION,
"Cannot start: local bounds not set!");
299 ESP_LOGW(
TAG_MOTION,
"Cycle count reached. Reset cycles or set new target to continue.");
307 auto standstill_result =
driver_->rampControl.IsStandstill();
308 if (!standstill_result || !standstill_result.Value()) {
309 ESP_LOGW(
TAG_MOTION,
"Motor not at standstill, stopping before start...");
311 vTaskDelay(pdMS_TO_TICKS(200));
314 while (checks < 20) {
316 auto check_result =
driver_->rampControl.IsStandstill();
317 if (check_result && check_result.Value()) {
320 vTaskDelay(pdMS_TO_TICKS(100));
337 const float vstart_rpm = MotorConfig::RAMP_VSTART_RPM;
338 const float vstop_rpm = MotorConfig::RAMP_VSTOP_RPM;
363 (void)
driver_->stallGuard.EnableStopOnStall(
false);
367 (void)
driver_->motorControl.SetStealthChopEnabled(
true);
372 if (resuming_from_pause) {
376 ESP_LOGI(
TAG_MOTION,
"Resuming from pause - continuing motion from current position");
385 float current_pos_deg = current_pos_result.IsOk() ? current_pos_result.Value() : 0.0f;
391 float dist_to_home = fabsf(current_pos_deg - home_pos);
392 float dist_to_min = fabsf(current_pos_deg - min_pos_deg);
393 float dist_to_max = fabsf(current_pos_deg - max_pos_deg);
398 constexpr float NEAR_THRESHOLD_DEG = 2.0f;
399 if (dist_to_home < NEAR_THRESHOLD_DEG) {
402 target_pos = max_pos_deg;
403 }
else if (dist_to_max < NEAR_THRESHOLD_DEG) {
406 target_pos = min_pos_deg;
407 }
else if (dist_to_min < NEAR_THRESHOLD_DEG) {
410 target_pos = max_pos_deg;
414 target_pos = (dist_to_min <= dist_to_max) ? min_pos_deg : max_pos_deg;
428 ESP_LOGI(
TAG_MOTION,
"Starting fatigue test (cycles: %lu/%lu)", current_cycles,
429 target_cycles == 0 ? 0xFFFFFFFF : target_cycles);
430 ESP_LOGI(
TAG_MOTION,
" Motion: Point-to-point mode (center->MAX->MIN), VMAX=%.1f RPM, AMAX=%.2f rev/s², Est.Freq=%.2f Hz",
433 ESP_LOGI(
TAG_MOTION,
" Cycle counting: one cycle = center -> MAX -> MIN (counted at MIN)");
448 ESP_LOGI(
TAG_MOTION,
"Paused fatigue test motion - current move will finish, then hold");
465 while (checks < 50) {
467 auto standstill_result =
driver_->rampControl.IsStandstill();
468 if (standstill_result.IsOk() && standstill_result.Value()) {
471 vTaskDelay(pdMS_TO_TICKS(100));
478 if (current_pos_result.IsOk()) {
479 float current_pos = current_pos_result.Value();
485 ESP_LOGI(
TAG_MOTION,
"Stopped fatigue test motion (cycles completed: %lu)", cycles);
505 ESP_LOGW(
TAG_MOTION,
"Cannot estimate frequency (dist=%.2f°, vmax=%.1f RPM, amax=%.2f rev/s²)",
511 float vmax_deg_per_s =
vmax_rpm_ * 360.0f / 60.0f;
517 float time_to_vmax_s = vmax_deg_per_s / amax_deg_per_s2;
520 float distance_during_accel_deg = 0.5f * amax_deg_per_s2 * time_to_vmax_s * time_to_vmax_s;
524 if (2.0f * distance_during_accel_deg >= distance_deg) {
528 leg_time_s = 2.0f * sqrtf(distance_deg / amax_deg_per_s2);
532 float cruise_distance_deg = distance_deg - 2.0f * distance_during_accel_deg;
533 float cruise_time_s = cruise_distance_deg / vmax_deg_per_s;
534 leg_time_s = 2.0f * time_to_vmax_s + cruise_time_s;
541 float cycle_period_s = 2.0f * leg_time_s + total_dwell_s;
545 ESP_LOGI(
TAG_MOTION,
"Estimated Frequency: %.2f Hz (dist=%.1f°, leg=%.3fs, dwell=%.1fs)",
547 ESP_LOGI(
TAG_MOTION,
" Using VMAX=%.1f RPM (%.1f°/s), AMAX=%.2f rev/s² (%.1f°/s²)",
567 ESP_LOGW(
TAG_MOTION,
"Local bounds clipped to global bounds");
584 auto target_reached =
driver_->rampControl.IsTargetReached();
585 if (target_reached.IsOk() && target_reached.Value()) {
605 if (current_pos.IsOk()) {
613 ESP_LOGI(
TAG_MOTION,
"Target cycle count reached: %lu cycles. Stopped and holding.", cycles);
621 float min_bound, max_bound;
622 uint32_t dwell_min, dwell_max;
623 uint32_t target_cycles;
636 bool target_reached =
false;
638 auto reached_result =
driver_->rampControl.IsTargetReached();
639 target_reached = reached_result.IsOk() && reached_result.Value();
644 float current_pos_deg = current_pos_result.IsOk() ? current_pos_result.Value() : 0.0f;
645 float dist_to_min = fabsf(current_pos_deg - min_bound);
646 float dist_to_max = fabsf(current_pos_deg - max_bound);
647 constexpr float NEAR_THRESHOLD_DEG = 2.0f;
650 switch (current_state) {
656 if (target_reached || dist_to_max < NEAR_THRESHOLD_DEG) {
679 uint32_t current_time_ms = esp_timer_get_time() / 1000;
680 uint32_t dwell_start;
689 if (current_time_ms - dwell_start >= dwell_max) {
703 if (target_reached || dist_to_min < NEAR_THRESHOLD_DEG) {
706 bool should_stop =
false;
721 if (target_cycles > 0 && new_cycles >= target_cycles) {
742 ESP_LOGI(
TAG_MOTION,
"Target cycle count reached: %lu cycles. Stopping.", new_cycles);
745 ESP_LOGI(
TAG_MOTION,
"Cycle %lu completed at MIN position (target: %lu)",
746 new_cycles, target_cycles == 0 ? 0xFFFFFFFF : target_cycles);
753 uint32_t current_time_ms = esp_timer_get_time() / 1000;
754 uint32_t dwell_start;
763 if (current_time_ms - dwell_start >= dwell_min) {
805#ifndef FATIGUE_MOTION_HEADER_INCLUDED
ESP32 FreeRTOS mutex wrapper for TMC51x0 driver.
Definition esp32_tmc_mutex.hpp:24
Unified fatigue test motion controller.
Definition fatigue_motion.hpp:26
void Pause() noexcept
Pause active motion (stops movement but keeps motor energized for resume).
Definition fatigue_motion_impl.hpp:439
void GetLocalBoundsFromCenterDegrees(float &min_degrees, float &max_degrees) const noexcept
Read local bounds (degrees).
Definition fatigue_motion_impl.hpp:162
bool bounded_
Definition fatigue_motion.hpp:252
uint32_t GetCurrentCycles() const noexcept
Get current completed cycles.
Definition fatigue_motion_impl.hpp:258
void RecalculateEstimatedFrequency() noexcept
Definition fatigue_motion_impl.hpp:493
float local_max_bound_
Definition fatigue_motion.hpp:250
uint32_t GetTargetCycles() const noexcept
Get configured target cycles.
Definition fatigue_motion_impl.hpp:263
Esp32TmcMutex & driver_mutex_
Definition fatigue_motion.hpp:277
uint32_t target_cycles_
Definition fatigue_motion.hpp:264
uint32_t dwell_at_min_ms_
Definition fatigue_motion.hpp:258
MotionState state_
Definition fatigue_motion.hpp:270
void Update() noexcept
Periodic update tick.
Definition fatigue_motion_impl.hpp:572
bool SetDwellTimes(uint32_t dwell_at_min_ms, uint32_t dwell_at_max_ms) noexcept
Set dwell times at local bounds.
Definition fatigue_motion_impl.hpp:230
tmc51x0::TMC51x0< Esp32SPI > * driver_
Definition fatigue_motion.hpp:244
~FatigueTestMotion() noexcept
Destructor.
void ClipLocalBoundsToGlobal() noexcept
Definition fatigue_motion_impl.hpp:551
float GetMaxVelocity() const noexcept
Get configured maximum velocity (RPM).
Definition fatigue_motion_impl.hpp:194
float GetAcceleration() const noexcept
Get configured acceleration (rev/s²).
Definition fatigue_motion_impl.hpp:220
void GetGlobalBoundsDegrees(float &min_degrees, float &max_degrees) const noexcept
Read current global bounds (degrees).
Definition fatigue_motion_impl.hpp:80
bool SetAcceleration(float amax_rev_s2) noexcept
Set acceleration for oscillation (direct TMC5160 AMAX control).
Definition fatigue_motion_impl.hpp:199
void ResetCycles() noexcept
Reset cycle counter and completion flags.
Definition fatigue_motion_impl.hpp:273
void SetUnbounded(float current_position_degrees, float default_range_degrees=175.0f) noexcept
Mark the system as unbounded and establish a default travel window.
Definition fatigue_motion_impl.hpp:86
float vmax_rpm_
Definition fatigue_motion.hpp:256
uint32_t current_cycles_
Definition fatigue_motion.hpp:265
bool IsRunning() const noexcept
Whether the motion controller is currently active.
Definition fatigue_motion_impl.hpp:488
bool Start() noexcept
Start active motion.
Definition fatigue_motion_impl.hpp:287
float amplitude_
Definition fatigue_motion.hpp:255
bool IsBounded() const noexcept
Whether the controller is in bounded mode (mechanical stops found).
Definition fatigue_motion_impl.hpp:282
float local_min_bound_
Definition fatigue_motion.hpp:249
FatigueTestMotion(tmc51x0::TMC51x0< Esp32SPI > *driver, Esp32TmcMutex &driver_mutex) noexcept
Construct a motion controller bound to a TMC51x0 driver instance.
Definition fatigue_motion_impl.hpp:45
uint32_t dwell_start_time_ms_
Definition fatigue_motion.hpp:271
MotionState
Definition fatigue_motion.hpp:269
float home_position_
Definition fatigue_motion.hpp:251
bool IsCycleComplete() const noexcept
Check whether the configured target cycle count has been reached.
Definition fatigue_motion_impl.hpp:268
Status GetStatus() const noexcept
Snapshot current controller status.
Definition fatigue_motion_impl.hpp:782
void Stop() noexcept
Stop active motion and command driver stop.
Definition fatigue_motion_impl.hpp:451
float global_min_bound_
Definition fatigue_motion.hpp:247
uint64_t start_time_us_
Definition fatigue_motion.hpp:261
float estimated_frequency_hz_
Definition fatigue_motion.hpp:274
void GetDwellTimes(uint32_t &dwell_at_min_ms, uint32_t &dwell_at_max_ms) const noexcept
Get dwell times at local bounds.
Definition fatigue_motion_impl.hpp:243
bool SetMaxVelocity(float vmax_rpm) noexcept
Set maximum velocity for oscillation (direct TMC5160 VMAX control).
Definition fatigue_motion_impl.hpp:174
uint32_t dwell_at_max_ms_
Definition fatigue_motion.hpp:259
bool SetLocalBoundsFromCenterDegrees(float min_degrees_from_center, float max_degrees_from_center, float edge_backoff_deg=3.5f) noexcept
Set local oscillation bounds (degrees) relative to the center.
Definition fatigue_motion_impl.hpp:105
float amax_rev_s2_
Definition fatigue_motion.hpp:257
bool SetTargetCycles(uint32_t cycles) noexcept
Set target cycle count (0 = infinite).
Definition fatigue_motion_impl.hpp:249
float global_max_bound_
Definition fatigue_motion.hpp:248
bool running_
Definition fatigue_motion.hpp:260
bool cycle_complete_
Definition fatigue_motion.hpp:266
float GetEstimatedCycleFrequency() const noexcept
Get estimated cycle frequency based on current velocity/accel/distance.
Definition fatigue_motion_impl.hpp:225
RAII mutex guard for automatic lock/unlock.
Definition esp32_tmc_mutex.hpp:63
void unlock() noexcept
Manually unlock mutex.
Definition esp32_tmc_mutex.hpp:85
Main class representing a TMC51x0 stepper motor driver (TMC5130 & TMC5160)
Definition tmc51x0.hpp:119
ESP32 GPIO pin configuration and compile-time configuration for TMC51x0 driver (TMC5130 & TMC5160)
Unified fatigue test motion controller.
static const char * TAG_MOTION
Definition fatigue_motion_impl.hpp:28
Definition fatigue_motion.hpp:18
static constexpr float AMAX_MAX_REV_S2
Definition fatigue_motion_impl.hpp:172
static constexpr float AMAX_MIN_REV_S2
Definition fatigue_motion_impl.hpp:171
static constexpr float VMAX_MIN_RPM
Definition fatigue_motion_impl.hpp:169
static constexpr float VMAX_MAX_RPM
Definition fatigue_motion_impl.hpp:170
@ RevPerSec
Revolutions per Second (recommended default for velocity)
@ Deg
Degrees (per second for velocity, per second^2 for accel)
@ RPM
Revolutions per Minute (Velocity only, typically)
@ HOLD
Velocity remains unchanged, unless stop event occurs.
@ POSITIONING
Positioning mode using all A, D and V parameters.
Status structure containing current motion controller state.
Definition fatigue_motion.hpp:31
Base template for test rig configuration (undefined - forces explicit specialization)
Definition esp32_tmc51x0_test_config.hpp:996