Summary
During a high-speed motor control stress test, we observed significant instability in the PID velocity loop, leading to erratic oscillations and unexpected hardware shutdowns. The investigation revealed that the control loop was relying on a naive calculation of Back Electromotive Force (Back EMF) that failed to account for the high-frequency switching nature of Pulse Width Modulation (PWM) and the dynamic nature of internal resistance. The failure resulted from treating a highly dynamic, non-linear electrical system as a static DC circuit.
Root Cause
The failure originated from three distinct technical oversights:
- Sampling Aliasing: The microcontroller was sampling current and voltage at arbitrary intervals that coincided with the PWM switching transitions, leading to “phantom” readings that did not represent the true average state.
- Static Resistance Assumption: The system used a pre-calculated value for internal resistance (R), failing to account for the fact that winding resistance changes significantly due to Joule heating during operation.
- Mathematical Simplification Error: The implementation used a simple subtraction method ($E = V_{applied} – I \times R$) without filtering the inductive voltage spikes inherent in motor commutation.
Why This Happens in Real Systems
In textbook theory, $V = IR + E$ is a clean linear equation. In production-grade power electronics, several factors complicate this:
- PWM Duty Cycle Variance: The “applied voltage” is not a constant DC value but a high-frequency square wave. If you sample during the OFF cycle, your voltage reading is zero; if you sample during the ON cycle, you are seeing the raw rail voltage, not the effective voltage.
- Inductive Kickback: Motors are massive inductors. Rapid changes in current cause $L(di/dt)$ voltage drops that can dwarf the Back EMF signal during switching transitions.
- Thermal Drift: As the motor runs, the copper windings heat up, increasing resistance. If your $R$ value is static, your Back EMF calculation will drift over time, causing the PID loop to become over- or under-compensated.
Real-World Impact
- Control Loop Instability: Incorrect Back EMF estimation leads to inaccurate speed feedback, causing the motor to overshoot or oscillate violently.
- Hardware Damage: Inaccurate compensation can lead to over-current conditions if the controller attempts to force a voltage that the motor cannot support at high speeds.
- Reduced Battery Life: In mobile robotics, inefficient control loops lead to wasted energy through constant, unnecessary micro-corrections.
Example or Code (if necessary and relevant)
import time
class MotorEstimator:
def __init__(self, R_nominal, k_constant):
self.R = R_nominal
self.k = k_constant
self.temp_coefficient = 0.0039 # Copper temp coefficient
self.reference_temp = 25.0
self.current_temp = 25.0
def update_resistance(self, measured_temp):
"""Adjust R based on temperature to prevent Back EMF drift."""
self.current_temp = measured_temp
self.R = self.R_nominal * (1 + self.temp_coefficient * (self.current_temp - self.reference_temp))
def calculate_back_emf(self, v_bus, duty_cycle, i_avg, is_on_pulse):
"""
Calculate Back EMF.
v_bus: Actual DC rail voltage.
duty_cycle: 0.0 to 1.0.
i_avg: Averaged current measured over a full PWM period.
is_on_pulse: Boolean indicating if we are sampling during the ON phase.
"""
# Effective voltage during the ON phase
v_effective = v_bus * duty_cycle
# Back EMF = V_effective - (I * R)
# Note: This assumes I is the average current during the ON period
e_back = v_effective - (i_avg * self.R)
return max(0, e_back)
# Simulation of a single control step
estimator = MotorEstimator(R_nominal=1.5, k_constant=0.05)
estimator.update_resistance(measured_temp=55.0) # Motor has warmed up
e_val = estimator.calculate_back_emf(v_bus=12.0, duty_cycle=0.8, i_avg=2.1, is_on_pulse=True)
print(f"Estimated Back EMF: {e_val:.2f}V")
How Senior Engineers Fix It
To move from a “hobbyist” implementation to a “production” implementation, we apply these strategies:
- Synchronous Sampling: We trigger the ADC (Analog-to-Digital Converter) at a fixed offset from the PWM timer. Specifically, we sample in the middle of the ON pulse to avoid the switching noise at the leading and trailing edges.
- Moving Average/Kalman Filtering: We never use a single-point sample. We use a running average of the current and voltage over multiple PWM cycles to extract the DC component from the AC ripple.
- Thermal Modeling: We implement a temperature-compensated resistance model or periodically re-calibrate $R$ during “zero-current” periods (e.g., when the motor is coasting) to maintain accuracy.
- Effective Voltage Calculation: We always calculate Back EMF using the effective average voltage ($V_{bus} \times \text{DutyCycle}$) rather than the instantaneous rail voltage.
Why Juniors Miss It
- Ignoring the Time Domain: Juniors often treat electrical equations as algebraic truths rather than differential equations. They forget that $V$ and $I$ are functions of time, not constants.
- The “Ideal Component” Fallacy: They assume the motor resistance is a fixed number found in a datasheet, ignoring the thermodynamics of the system.
- Sampling Blindness: They assume that calling
analogRead()at any time is sufficient, failing to realize that in a switching environment, when you sample is more important than how often you sample.