On this page
Torque control implementation v2.4+
BLDC motors Stepper motors Hybrid Stepper motors
The SimpleFOClibrary implements 4 different torque control strategies, selected via the motor.torque_controller parameter. All torque control modes are executed within the loopFOC() function and convert the current setpoint (current_sp) into voltage commands (voltage.q and voltage.d) that are applied to the motor.
Torque Control Modes
// Torque control type
enum TorqueControlType : uint8_t {
voltage = 0x00, // Torque control using voltage
dc_current = 0x01, // Torque control using DC current
foc_current = 0x02, // Torque control using dq currents
estimated_current = 0x03 // Torque control using estimated current
};
The torque control mode is selected by setting:
motor.torque_controller = TorqueControlType::voltage; // or dc_current, foc_current, estimated_current
Voltage Mode
Type: TorqueControlType::voltage
The simplest torque control method, suitable when current sensing is not available. It assumes that voltage is proportional to current, which is proportional to torque.
BLDC motors Stepper motors Hybrid Stepper motors
Implementation
From FOCMotor::loopFOC():
case TorqueControlType::voltage:
voltage.q = _constrain(current_sp, -voltage_limit, voltage_limit) + feed_forward_voltage.q;
voltage.d = feed_forward_voltage.d;
break;
Configuration
motor.torque_controller = TorqueControlType::voltage;
motor.voltage_limit = 12.0; // Maximum voltage [V]
motor.feed_forward_voltage.q = 0.0; // Optional feed-forward
motor.feed_forward_voltage.d = 0.0;
See the voltage control API documentation See a deeper dive in voltage control theory
Estimated Current Mode
Type: TorqueControlType::estimated_current
BLDC motors Stepper motors Hybrid Stepper motors
Estimates the current based on motor parameters (phase resistance and KV rating) without requiring a current sensor. Provides better torque control than voltage mode but requires accurate motor parameters.
Implementation
From FOCMotor::loopFOC():
case TorqueControlType::estimated_current:
if(!_isset(phase_resistance)) return;
// constrain current setpoint
current_sp = _constrain(current_sp, -current_limit, current_limit) + feed_forward_current.q;
// calculate the back-emf voltage if KV_rating available
if (_isset(KV_rating)) voltage_bemf = estimateBEMF(shaft_velocity);
// filter the current estimate
current.q = LPF_current_q(current_sp);
// calculate the phase voltage: V = I*R + V_bemf
voltage.q = current.q * phase_resistance + voltage_bemf;
// constrain voltage
voltage.q = _constrain(voltage.q, -voltage_limit, voltage_limit) + feed_forward_voltage.q;
// d voltage - lag compensation
if(_isset(axis_inductance.q))
voltage.d = _constrain(-current_sp*shaft_velocity*pole_pairs*axis_inductance.q,
-voltage_limit, voltage_limit) + feed_forward_voltage.d;
else voltage.d = feed_forward_voltage.d;
break;
Motor Model
The voltage calculation is based on the motor electrical model:
\[u_q = R \cdot i_q + K_e \cdot \omega\]Where:
- \(u_q\) : Phase voltage
- \(i_q \cdot R\) : Resistive voltage drop
- \(K_e \cdot \omega = u_{BEMF}\) : Back-EMF voltage (calculated from velocity and KV rating)
Additionally the inductive voltage drop can be compensated if the motor inductance is known:
\[u_d = -L_q \cdot i_q \cdot \omega\]Configuration
motor.torque_controller = TorqueControlType::estimated_current;
motor.current_limit = 5.0; // Maximum current [A]
motor.voltage_limit = 12.0; // Maximum voltage [V]
// Required motor parameters
motor.phase_resistance = 2.5; // Phase resistance [Ohm]
motor.KV_rating = 100; // Motor KV rating [rpm/V] or provide via motor constructor
// Low pass filter velocity for BEMF compensation
motor.LPF_velocity.Tf = 0.01; // 10ms time constant
// Optional: lag compensation
motor.axis_inductance.q = 0.00015; // [H]
// Optional: feed-forward
motor.feed_forward_voltage.q = 0.0;
motor.feed_forward_voltage.d = 0.0;
Motor Parameter Identification
To use this mode effectively, you need to know your motor parameters. The library provides a helper function that uses current sensing to measure the motor’s phase resistance and inductance, which can then be used for better estimation even without current sensing in normal operation.
// In setup(), after motor.init() and motor.initFOC():
motor.characteriseMotor(voltage_test); // Measures R, L
See a deeper dive in estimated current control theory See the estimated current control API documentation Low-pass filter implementation
DC Current Mode
Type: TorqueControlType::dc_current
BLDC motors Stepper motors Hybrid Stepper motors
Uses current sensing to measure the overall current magnitude and regulates it with a PID controller. Requires a current sensor but only measures total current, not individual phase currents.
Implementation
From FOCMotor::loopFOC():
case TorqueControlType::dc_current:
if(!current_sense) return;
// constrain current setpoint
current_sp = _constrain(current_sp, -current_limit, current_limit) + feed_forward_current.q;
// read overall current magnitude
current.q = current_sense->getDCCurrent(electrical_angle);
// filter the value
current.q = LPF_current_q(current.q);
// calculate the phase voltage using PID
voltage.q = PID_current_q(current_sp - current.q) + feed_forward_voltage.q;
// d voltage - lag compensation
if(_isset(axis_inductance.q))
voltage.d = _constrain(-current_sp*shaft_velocity*pole_pairs*axis_inductance.q,
-voltage_limit, voltage_limit) + feed_forward_voltage.d;
else voltage.d = feed_forward_voltage.d;
break;
Configuration
motor.torque_controller = TorqueControlType::dc_current;
motor.current_limit = 5.0; // Maximum current [A]
motor.voltage_limit = 12.0;
// PID controller for current loop
motor.PID_current_q.P = 5.0;
motor.PID_current_q.I = 300.0;
motor.PID_current_q.D = 0.0;
motor.PID_current_q.output_ramp = 1e6;
motor.PID_current_q.limit = motor.voltage_limit;
// Low pass filter for current measurement
motor.LPF_current_q.Tf = 0.005; // 5ms time constant
// Optional: lag compensation (requires motor parameter)
motor.axis_inductance.q = 0.00015; // [H]
Advanced Features
Lag Compensation: Compensates for the phase lag introduced by motor inductance at high speeds, based on the motor model: \(u_d = -L_q \cdot \frac{di_q}{dt} \approx -L_q \cdot i_q \cdot \omega\)
Or in code:
voltage.d -= current_sp * shaft_velocity * pole_pairs * axis_inductance.q;
See the DC current control API documentation PID controller implementation Low-pass filter implementation
FOC Current Mode
Type: TorqueControlType::foc_current
BLDC motors Stepper motors Hybrid Stepper motors
The most accurate torque control method. Measures and controls both d and q axis currents independently using two PID controllers. This is true Field Oriented Control with full current feedback.
Implementation
From FOCMotor::loopFOC():
case TorqueControlType::foc_current:
if(!current_sense) return;
// constrain current setpoint
current_sp = _constrain(current_sp, -current_limit, current_limit) + feed_forward_current.q;
// read dq currents
current = current_sense->getFOCCurrents(electrical_angle);
// filter values
current.q = LPF_current_q(current.q);
current.d = LPF_current_d(current.d);
// calculate the phase voltages using two PIDs
voltage.q = PID_current_q(current_sp - current.q);
voltage.d = PID_current_d(feed_forward_current.d - current.d);
// lag compensation
if(_isset(axis_inductance.q))
voltage.d = _constrain(voltage.d - current_sp*shaft_velocity*pole_pairs*axis_inductance.q,
-voltage_limit, voltage_limit);
// cross coupling compensation
if(_isset(axis_inductance.d))
voltage.q = _constrain(voltage.q + current.d*shaft_velocity*pole_pairs*axis_inductance.d,
-voltage_limit, voltage_limit);
// add feed forward
voltage.q += feed_forward_voltage.q;
voltage.d += feed_forward_voltage.d;
break;
Configuration
motor.torque_controller = TorqueControlType::foc_current;
motor.current_limit = 5.0; // Maximum current [A]
motor.voltage_limit = 12.0;
// Q-axis PID controller (torque-producing current)
motor.PID_current_q.P = 5.0;
motor.PID_current_q.I = 300.0;
motor.PID_current_q.D = 0.0;
motor.PID_current_q.output_ramp = 1e6;
motor.PID_current_q.limit = motor.voltage_limit;
// D-axis PID controller (flux-producing current)
motor.PID_current_d.P = 5.0;
motor.PID_current_d.I = 300.0;
motor.PID_current_d.D = 0.0;
motor.PID_current_d.output_ramp = 1e6;
motor.PID_current_d.limit = motor.voltage_limit;
// Low pass filters
motor.LPF_current_q.Tf = 0.005; // 5ms
motor.LPF_current_d.Tf = 0.005; // 5ms
// Optional: motor inductances for compensation
motor.axis_inductance.q = 0.00015; // [H]
motor.axis_inductance.d = 0.00015; // [H]
// Optional: feed-forward
motor.feed_forward_current.d = 0.0; // Usually 0 for surface-mount motors
motor.feed_forward_voltage.q = 0.0;
motor.feed_forward_voltage.d = 0.0;
PID controller implementation Low-pass filter implementation
Advanced Features
Lag Compensation:
Based on the motor electrical model, the inductive voltage drop can be compensated to improve high-speed performance:
\[u_d = -L_q \cdot \frac{di_q}{dt} \approx -L_q \cdot i_q \cdot \omega\]Compensates for the phase lag introduced by motor inductance at high speeds:
voltage.d -= current_sp * shaft_velocity * pole_pairs * axis_inductance.q;
Cross-Coupling Compensation:
Based on the motor model, the interaction between d and q axes can be compensated to improve accuracy:
\[u_q = R \cdot i_q + K_e \cdot \omega + L_d\frac{di_d}{dt} \approx R \cdot i_q + K_e \cdot \omega + L_d \cdot i_d \cdot \omega\]Compensates for the interaction between d and q axes:
voltage.q += current.d * shaft_velocity * pole_pairs * axis_inductance.d;
See the FOC algorithm theory documentation
Quick Selection Guide
Choose Voltage Mode if:
- No current sensing available
- Low-power applications (gimbal motors)
- Cost is primary concern
- Basic control is sufficient
Choose DC Current Mode if:
- Single shunt or DC link sensing available
- Need current limiting
- Cost-sensitive but need better accuracy than voltage
Choose FOC Current Mode if:
- Highest performance required
- Current sensing hardware available
- Applications requiring precise torque control
- High-speed operation with field weakening
Choose Estimated Current Mode if:
- No current sensing available
- Motor parameters are well known
- Need current limiting without hardware
- Better accuracy than voltage mode required
See more info in the torque control API documentation
Implementation in loopFOC()
All torque control modes are executed in the loopFOC() function. Here’s the overall flow:
void FOCMotor::loopFOC() {
// Update sensor
if (sensor) sensor->update();
// If disabled or not ready, return
if(!enabled || motor_status != FOCMotorStatus::motor_ready) return;
// Calculate electrical angle
electrical_angle = electricalAngle();
// Execute torque control based on selected mode
switch (torque_controller) {
case TorqueControlType::voltage:
// Direct voltage control
break;
case TorqueControlType::dc_current:
// DC current control with PID
break;
case TorqueControlType::foc_current:
// Full FOC with d/q current control
break;
case TorqueControlType::estimated_current:
// Model-based current estimation
break;
}
// Apply calculated voltages to motor phases
setPhaseVoltage(voltage.q, voltage.d, electrical_angle);
}
The setPhaseVoltage() function then uses the FOC algorithm to convert d/q voltages into three-phase voltages (or two-phase for stepper motors) and applies them via PWM.
See the FOC algorithm implementation docs
Related Documentation
Motion Control Implementation PID Implementation Low-Pass Filter Implementation




