Fatigue Testing: ESP-NOW Controlled Back-and-Forth Motion Between Bounds
Overview
The current fatigue test implementation is the fatigue_test_espnow_unit example under main/fatigue_test_espnow/.
It implements a two-device system:
- Fatigue Test Unit (this project):
- Drives the TMC51x0 (TMC5160) stepper driver
- Performs bounds finding (StallGuard2 or encoder-based)
- Runs the back-and-forth fatigue motion between safe bounds
- Communicates over ESP-NOW and UART for control and diagnostics
- Remote GUI Controller (M5Dial):
- Provides the user interface (touch + encoder)
- Sends configuration and commands over ESP-NOW
- Displays live status from the fatigue test unit
For detailed architecture and protocol information, see the docs under main/fatigue_test_espnow/docs/:
README.mdβ High-level ESP-NOW system overviewPAIRING_PROTOCOL.mdβ Secure pairing and HMAC authenticationBOUNDS_CACHING.mdβ Bounds cache and deβenergize behavior
Legacy single-board UART-only examples (fatigue_test_stallguard.cpp, fatigue_test_encoder.cpp) have been replaced by fatigue_test_espnow_unit.
Purpose
This system is ideal for:
- Fatigue / endurance testing of cables, flexures, and mechanical joints
- Back-and-forth oscillatory motion between mechanically detected bounds
- Safe, repeatable motion with StallGuard2 or encoder-based bounds detection
- Remote operation from a GUI board (M5Dial) via ESP-NOW
- Fast re-start workflows using a bounds cache and motor de-energize timer
Key Features (Fatigue Test Unit)
1. Smart Bounds Finding with Safety Limits
- Dual method support:
- StallGuard2-based sensorless bounds detection (
g_use_stallguard = true) - Encoder-based bounds detection (
g_use_stallguard = false)
- StallGuard2-based sensorless bounds detection (
- Safety travel limits:
- Bounds search is constrained by a maximum travel span
- If travel exceeds this span, or a TMC fault/reset is detected, bounds finding aborts and reports an error
- Fault- and mode-aware:
- Monitors
GSTAT(reset, UV_CP, DRV_ERR) during bounds finding - Aborts if the driver falls back to StealthChop while StallGuard2 is expected
- Monitors
2. Home Offset and Oscillation Range
After successful bounds finding:
- Computes the geometric center between min and max mechanical bounds
- Applies a configurable center offset to avoid stopping on a full-step detent:
BOUNDS_FINDING_CENTER_OFFSET_DEGinmain/fatigue_test_espnow/main.cpp- Default: small move toward the negative (min) direction after homing
- Tightens the oscillation range inside the mechanical limits:
OSCILLATION_EDGE_BACKOFF_DEGdefines how far inside the mechanical bounds the fatigue motion runs
3. Frequency-Tuned Point-to-Point Motion
The FatigueTest::FatigueTestMotion class manages point-to-point motion between the configured bounds:
- Uses TMC internal ramp positioning mode (
RampMode::POSITIONING) - Trapezoidal motion profile:
- 1/3 accel, 1/3 constant velocity, 1/3 decel for smooth motion
- Parameters are driven from configuration (
g_settings.test_unit):- Max velocity in RPM (
oscillation_vmax_rpm) - Acceleration / deceleration in rev/sΒ² (
oscillation_amax_rev_s2) - Target cycle count (
cycle_amount) - Local bounds around center
- Max velocity in RPM (
- Uses both
IsTargetReached()andIsStandstill()before redefining zero or marking a move complete
4. Bounds Cache and De-Energize Timer
The BoundsCache namespace in main.cpp implements a time-based cache:
- Bounds validity window:
- Default
DEFAULT_VALIDITY_MINUTES = 2(configurable) - While valid, START commands can skip new bounds finding and reuse cached bounds
- Default
- Motor de-energize timer:
- After bounds finding, the motor stays energized during the validity window
- A timer de-energizes the motor after the window expires to prevent heating
- Status reporting:
GetBoundsValidFlag_()exposes cache state to the GUI (1 = valid, 0 = expired)
5. ESP-NOW Communication with GUI Board
Communication is defined in espnow_protocol.hpp and handled by espnow_receiver.cpp:
- Message types (examples):
- Configuration updates (bounds, velocity, acceleration, cycles, cache time)
- Control commands: START, STOP, PAUSE, RESUME, BOUNDS_FIND, RESET
- Status updates: internal state, bounds valid flag, cycle counts, fault info
- Pairing and security:
- Uses a shared HMAC-based pairing secret (injected via
secrets.local.ymlor--secret) - Pairing protocol is documented in
fatigue_test_espnow/docs/PAIRING_PROTOCOL.md
- Uses a shared HMAC-based pairing secret (injected via
- FreeRTOS task separation:
espnow_command_taskβ receives and enqueues commandsmotion_control_taskβ applies motion settings and runs the state machinestatus_update_taskβ periodically sends status to the GUI controllerbounds_finding_taskβ performs bounds finding in a dedicated task
6. UART Console (Optional Local Control)
The fatigue test unit also exposes a UART console (typically over USB serial) for:
- Debug logging (TMC status, bounds finding progress, motion state)
- Manual commands for development (pairing, bounds finding, starting/stopping motion)
- Diagnostics when ESP-NOW or the GUI board is unavailable
Command names and options mirror the ESP-NOW protocol where possible. See fatigue_test_espnow/docs/README.md for the full list.
Hardware Requirements (Fatigue Test Unit)
- ESP32-C6 (or other supported target configured in
app_config.yml) - TMC5160 (or TMC51x0) stepper driver evaluation board
- Stepper motor (see Motor Configuration Guide)
- SPI connection between ESP32 and TMC51x0
- Optional encoder (for encoder-based bounds finding)
- Mechanical stops (for bounded mode)
- USB-serial connection (UART console)
- Power supply: 12β36 V DC with adequate current capacity
Pin Configuration
Default pin configuration (from esp32_tmc51x0_test_config.hpp):
- SPI: MOSI=6, MISO=2, SCLK=5, CS=18
- Control: EN=11
- Clock: CLK=10 (tied to GND for internal clock)
- Diagnostics: DIAG0=23, DIAG1=15
- UART: Uses default UART_NUM_0 (USB serial port)
- SPI Clock: 500 kHz (from config) or 1 MHz (sinusoidal example uses 1 MHz)
Test Rig Selection
Test rig selection is done via a static constexpr at the top of main/fatigue_test_espnow/main.cpp:
1
2
static constexpr tmc51x0_test_config::TestRigType SELECTED_TEST_RIG =
tmc51x0_test_config::TestRigType::TEST_RIG_FATIGUE;
Main rigs:
- TEST_RIG_FATIGUE (default): Applied Motion 5034-369 NEMA 34 motor, TMC51x0 EVAL board, reference switches, encoder
- TEST_RIG_CORE_DRIVER: 17HS4401S motor (geared or direct), TMC51x0 EVAL board, reference switches, encoder
The test rig automatically configures:
- Motor electrical parameters (current, sense resistors, microsteps)
- Chopper / StealthChop settings
- Encoder and reference switch wiring
- SPI clock and platform options
How It Works
Phase 1: Bounds Finding
- Position Reset: Resets motor position to 0 for accurate tracking
- Maximum Bound Search (positive direction):
- Commands motor to +360Β° position
- 360Β° Safety Limit: Monitors position continuously - if rotation exceeds 360Β° from start, immediately stops and uses default bounds
- Detects stall via StallGuard2 (stallguard variant) or encoder position monitoring (encoder variant)
- If stall detected: backs off 5Β° and records maximum bound
- If 360Β° reached without stall: marks as unbounded
- Minimum Bound Search (negative direction):
- Commands motor to -360Β° position
- 360Β° Safety Limit: Same safety check as maximum bound search
- Detects stall via StallGuard2 (stallguard variant) or encoder position monitoring (encoder variant)
- If stall detected: backs off 5Β° and records minimum bound
- If -360Β° reached without stall: marks as unbounded
- Unbounded Detection: If no stall detected in either direction at 360Β° limits, assumes unbounded and uses -175Β° to +175Β° default bounds
- StealthChop Restore: Switches back to StealthChop for normal operation (stallguard variant only)
Phase 2: Bounds Setup
If Bounded:
- Sets global bounds based on stall positions
- Moves to center position
- Sets home position to center
- Configures local bounds (default: 90% of global)
If Unbounded:
- No mechanical stops detected at Β±360Β° limits
- Sets global bounds as -175Β° to +175Β° (relative to center at 0Β°)
- Sets local bounds within global bounds (default: 90% of global range)
Phase 3: Motion Control
The FatigueTestMotion class manages the motion:
- Trajectory Calculation: Calculates VMAX and AMAX based on frequency and dwell times
- State Machine: Manages motion states:
MOVING_TO_MAX: Moving toward maximum boundMOVING_TO_MIN: Moving toward minimum boundDWELL_AT_MAX: Dwell at maximum boundDWELL_AT_MIN: Dwell at minimum bound
- Cycle Counting: Tracks cycles (min β max β min = 1 cycle)
- Target Reached: Stops at center when target cycle count reached
Trajectory Calculation
The system automatically calculates motion parameters to achieve the target frequency:
Formula
Given:
- Target frequency:
fHz - Travel distance:
Dsteps (one way) - Dwell times:
T_dwell_min,T_dwell_max(ms) - Note: Center dwell is not included in trajectory calculation (deprecated)
Calculate:
- Target Period:
T_period = 1/fseconds - Total Dwell:
T_dwell_total = (T_dwell_min + T_dwell_max) / 1000seconds (center dwell excluded) - Motion Time:
T_motion = T_period - T_dwell_totalseconds - Leg Time:
T_leg = T_motion / 2seconds (one way) - VMAX:
VMAX = 1.5 * D / T_legsteps/s (trapezoidal profile: 1/3 accel, 1/3 const, 1/3 decel) - AMAX:
AMAX = VMAX / (T_leg / 3)steps/sΒ²
Profile Shape
Uses trapezoidal profile:
- 1/3 time accelerating
- 1/3 time at constant velocity
- 1/3 time decelerating
This provides smooth motion while maximizing speed within the time constraint.
UART Command Interface
Command Format
Commands follow Linux-like argument structure:
1
<command> [arguments]
Available Commands
Frequency Control
1
-f <value> or --freq <value>
Sets oscillation frequency in Hz (0.0-10.0).
Example:
1
2
-f 0.5 # Set frequency to 0.5 Hz
--freq 1.0 # Set frequency to 1.0 Hz
Dwell Times
1
-d <min> <max> [center]
Sets dwell times in milliseconds at bounds. Note: Center dwell argument is accepted but ignored (feature deprecated - trajectory calculation uses no center dwell).
Example:
1
2
3
-d 2000 2000 # Dwell 2 seconds at min and max bounds
-d 1000 1500 # Dwell 1s at min, 1.5s at max
-d 2000 2000 500 # Center dwell argument (500ms) will be ignored with warning
Bounds
1
-b <min> <max> or --bounds <min> <max>
Sets angle bounds from center in degrees.
Example:
1
2
-b -60 60 # Set bounds to Β±60 degrees from center
--bounds -90 90 # Set bounds to Β±90 degrees from center
Cycle Count
1
-c <count> or --cycles <count>
Sets target cycle count (0 = infinite).
Example:
1
2
-c 1000 # Run for 1000 cycles
-c 0 # Run indefinitely
Actions
1
-a start|stop|reset
Control motion:
start: Start motionstop: Stop motionreset: Reset cycle count
Example:
1
2
3
-a start # Start motion
-a stop # Stop motion
-a reset # Reset cycle count to 0
Status
1
-s or --status
Shows current status including:
- Running state
- Bounded/unbounded status
- Frequency (target and estimated)
- Local and global bounds
- Cycle count
- Dwell times
Example:
1
2
-s # Show status
--status # Show status
Help
1
-h or --help
Shows help message with all available commands.
Example:
1
2
-h # Show help
--help # Show help
Command Examples
Complete Setup Sequence:
1
2
3
4
5
-b -60 60 # Set bounds to Β±60 degrees
-f 0.5 # Set frequency to 0.5 Hz
-d 2000 2000 # Set dwell times to 2 seconds
-c 1000 # Set target to 1000 cycles
-a start # Start motion
Real-Time Adjustment:
1
2
3
-a stop # Stop motion
-f 1.0 # Increase frequency to 1.0 Hz
-a start # Resume with new frequency
Status Check:
1
-s # Check current status
Expected Behavior
Startup Sequence
- Motor selection confirmation
- Driver initialization
- StallGuard2 configuration
- Motor enable
- Bounds Finding Phase:
- Searching negative directionβ¦
- Stall detected (or unbounded detected)
- Searching positive directionβ¦
- Stall detected (or unbounded detected)
- Bounds Setup:
- Bounded mode: Moving to center, setting home
- Unbounded mode: Setting default 5-355 range
- Default Configuration:
- Local bounds set
- Frequency set
- Dwell times set
- Status display
- UART Interface Ready:
- Command interface initialized
- Tasks started
- Ready for commands
During Operation
- Smooth back-and-forth motion between bounds
- Motion control task updates every 10ms
- UART command processing every 50ms
- Periodic status logging every 10 seconds
- Real-time parameter adjustment via UART
Motion Characteristics
- Smooth Acceleration/Deceleration: Trapezoidal profile ensures smooth motion
- Precise Timing: Frequency-tuned to match target frequency
- No Center Dwell: Continuous motion through center
- Accurate Cycles: Cycle counting at center crossing
Trajectory Calculation Details
Example Calculation
Given:
- Frequency: 0.5 Hz
- Bounds: Β±60 degrees (120 degrees total)
- Dwell: 2000ms at each bound
- Motor: 17HS4401S with gearbox (~265,216 steps/rev output)
Calculate:
- Distance: 120Β° = 88,405 steps (one way)
- Period: 1/0.5 = 2.0 seconds
- Dwell Total: 2.0 + 2.0 = 4.0 seconds
- Motion Time: 2.0 - 4.0 = Invalid! (frequency too high)
- Adjusted: System will warn and use minimum motion time
Corrected Example:
- Frequency: 0.2 Hz
- Dwell: 500ms at each bound
- Period: 5.0 seconds
- Dwell Total: 1.0 second
- Motion Time: 4.0 seconds
- Leg Time: 2.0 seconds
- VMAX: 1.5 Γ 88,405 / 2.0 = 66,304 steps/s
- AMAX: 66,304 / (2.0/3) = 99,456 steps/sΒ²
Troubleshooting
Bounds Not Found
Symptoms: System reports βUnbounded Modeβ
Solutions:
- Check if motor can actually rotate 360Β° (may be truly unbounded)
- StallGuard2 variant: Verify StallGuard2 is working (check SG_RESULT values)
- StallGuard2 variant: Ensure SpreadCycle mode is active during homing
- Encoder variant: Verify encoder is connected and reading position correctly
- Check motor current is adequate for stall detection
- Verify mechanical stops are present and functional
- Safety Limit Reached: If you see βSAFETY LIMIT: Motor rotated XΒ° (exceeds 360Β° limit)β, the motor exceeded the safety limit - check for position tracking issues or mechanical problems
Frequency Too High
Symptoms: Warning βRequested frequency is impossible with given dwell timesβ
Solutions:
- Reduce target frequency
- Reduce dwell times
- Increase travel distance (wider bounds)
- System will use maximum safe speed (frequency will be lower)
Motion Not Smooth
Symptoms: Jerky motion or overshoot
Solutions:
- Check VMAX/AMAX values (may be too high)
- Verify StealthChop is calibrated
- Check motor current settings
- Verify power supply stability
- Check for mechanical binding
UART Commands Not Working
Symptoms: Commands not recognized or not taking effect
Solutions:
- Verify UART baud rate (115200)
- Check serial port connection
- Verify command format (use
-hfor help) - Check if motion is running (some commands require stopped state)
- Verify command handler registration
Cycles Not Counting
Symptoms: Cycle count doesnβt increment
Solutions:
- Verify motion is actually reaching bounds
- Check if dwell times are preventing motion
- Verify cycle counting logic (counts at center crossing)
- Check motion state machine transitions
Code Structure
Main Components
- FatigueTestMotion Class: Manages motion state machine and trajectory calculation
- UartCommandParser Class: Handles UART command parsing and execution
- Motion Control Task: Updates motion state every 10ms
- UART Command Task: Processes commands every 50ms
- Main Loop: Provides periodic status logging
Key Classes and Functions
FatigueTestMotion::RecalculateTrajectory(): Calculates VMAX/AMAX from frequencyFatigueTestMotion::Start(): Starts motion with calculated parametersFatigueTestMotion::Update(): Updates motion state machineFatigueTestMotion::SetFrequency(): Updates frequency and recalculates trajectoryFatigueTestMotion::SetDwellTimes(): Updates dwell times and recalculates trajectoryUartCommandParser::ProcessCommand(): Parses and executes UART commands
Customization
Changing Default Parameters
Edit defaults in app_main():
1
2
3
4
5
6
7
8
// Default local bounds
motion.SetLocalBoundsFromCenterDegrees(-amplitude, amplitude);
// Default frequency (example)
motion.SetFrequency(0.5f);
// Default dwell times (example)
motion.SetDwellTimes(2000, 2000, 0);
Adding Custom Commands
Register new commands in app_main():
1
2
3
4
parser.RegisterCommand(
{"-x", "--custom", "Custom command description", 1, 1},
HandleCustomCommand
);
Modifying Trajectory Profile
Edit RecalculateTrajectory() to change profile shape:
1
2
3
// Change from 1/3-1/3-1/3 to different ratio
calculated_vmax_ = (2.0f * distance) / leg_time_s; // Different profile
calculated_amax_ = calculated_vmax_ / (leg_time_s / 4.0f);
Related Documentation
- Motor Configuration Guide - Motor selection
- Internal Ramp Sinusoidal Example - Simpler motion example
- Internal Ramp Comprehensive Test - Comprehensive StallGuard2, ramp control, and positioning testing
Example Output
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
I (1234) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
I (1235) FatigueTest: β TMC5160 Fatigue Test Platform: Bounds Finding & Sinuous Motion β
I (1236) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
I (1237) FatigueTest: Selected Motor: 17HS4401S with 5.18:1 gearbox
I (1238) FatigueTest: Driver initialized successfully
I (1239) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
I (1240) FatigueTest: β STEP 1: Finding Global Bounds β
I (1241) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
I (1242) FatigueTest: Finding minimum bound (negative direction)...
I (1243) FatigueTest: Stall detected at min bound! SG_RESULT=3
I (1244) FatigueTest: Finding maximum bound (positive direction)...
I (1245) FatigueTest: Stall detected at max bound! SG_RESULT=2
I (1246) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
I (1247) FatigueTest: β STEP 2: Setting Global Bounds and Home β
I (1248) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
I (1249) FatigueTest: === BOUNDED MODE ===
I (1250) FatigueTest: Global bounds: min=-45.00Β°, max=45.00Β° from center
I (1251) FatigueTest: Trajectory Recalculated: Dist=88405 steps, LegTime=2.000s
I (1252) FatigueTest: Target Freq=0.20Hz, Est Freq=0.20Hz
I (1253) FatigueTest: VMAX=66304.0, AMAX=99456.0
I (1254) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
I (1255) FatigueTest: β System Ready - Use UART Commands to Control β
I (1256) FatigueTest: ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ