On this page
Torque control using DC current
BLDC motors Stepper motors Hybrid Stepper motors
This control loop allows you to run BLDC and stepper motors like a current-controlled DC motors. It requires current sensing hardware. The user sets the target current \(i_d\) and the algorithm calculates the necessary q-axis voltage \(u_q\) to maintain it. This mode is enabled by:
// DC current torque control mode
motor.torque_controller = TorqueControlType::dc_current;
How does it work exactly
BLDC motors Stepper motors Hybrid Stepper motors
DC current control measures the overall current magnitude (the vector sum of phase currents) and controls it to match the target current \(i_q\) set by the user. Unlike FOC Current Mode, which runs two separate PID loops for the \(d\) and \(q\) axes, DC Current mode uses a single PI loop. It assumes the motor is running efficiently enough that the measured DC current is almost entirely composed of torque-producing \(q\)-axis current.
In this control mode the user sets the target q-axis current \(i_q\) and the FOC algorithm calculates the appropriate q-axis voltage to be applied to the motor to maintain the desired current \(u_q\). Instead of relying on the motor model (link in estimated current control), this algorithm measures the motor current and closes the control loop using a PI controller. As in FOC control case, this approach is much more robust and less prone to parameter inaccuracies, but it requires a current sensor to be implemented in the system.
The control law for the q-axis voltage is:
\[u_q = \text{PI}_q(i_q - \hat{i}_{DC}) = \text{PI}_q(i_q - \hat{i}_q)\]Where \(\hat{i}_{DC}\) is measured current which is considered to be equal to the measured q-axis current \(\hat{i}_q\). This assumption is valid if the d-axis current is negligible \(i_d \approx 0\), which is relatively reasonable assumption, especially at low speeds.
\[i_{DC} = \sqrt{i_d^2 + i_q^2} \approx i_q, \text{ if } i_d \approx 0\]The d-axis is controlled to be zero by setting the d-axis voltage to zero:
\[u_d = 0\]Using the PI controller to control the q-axis voltage allows the algorithm to maintain the desired current and compensates for the effect of the back-EMF voltage, which is proportional to the velocity of the motor. This means that the algorithm can maintain the desired current even at higher velocities, where the back-EMF voltage becomes significant. However it does not compensate for the cross-coupling term, which can lead to reduced performance at higher end of the velocity spectrum, as discussed in the next section.
The torque generated by the motor can be estimated as:
\[\hat{\tau} = K_t \hat{i}_q = K_t \hat{i}_{DC}\]Where \(K_t\) is the torque constant of the motor.
See a deeper dive in motor dynamics and FOC control teory See a deeper dive in the FOC transformations theory
Inductive lag compensation (Advanced)
BLDC motors Stepper motors Hybrid Stepper motors
As discussed in FOC theory corner mode, the d-axis current is not only proportional to the volatge \(u_d\), but also to the q-axis current \(i_q\) and the velocity of the motor through the cross-coupling term.
\[i_d = \frac{1}{R}(u_d + L_q i_q v_e)\]Where \(v_e\) is the electrical velocity of the motor (\(v_e = n_{PP} \cdot v\) , where \(n_{PP}\) is the number of pole pairs and \(v\) is the mechanical velocity). Therefore at higher velocities, the d-axis current becomes higher and the assumption that \(i_{DC}\)=\(i_q\) no longer holds, which can lead to reduced performance of this control mode.
In that case the real \(\hat{i}_q\) current can be calculated as:
\[\hat{i}_q = \sqrt{ \hat{i}_{DC}^2 - \hat{i}_d^2}\]Where the \(\hat{i}_d\) is the “real” d-axis current flowing in the motor. The term \(\hat{i}_{DC}\) represents the measured DC current. The true torque \(\hat{\tau}\) generated by the motor can then be estimated as:
\[\hat{\tau} = K_t \hat{i}_q = K_t \sqrt{ \hat{i}_{DC}^2 - \hat{i}_d^2}\]So the higher the d-axis current, the more the q-axis current is underestimated, which leads to reduced torque output of the motor. This is one of the main limitations of this control mode, but it can be mitigated by implementing a compensation for the cross-coupling term, as described in the next section.
In order to improve the performance of this control mode at higher velocities, the library implements a compensation for the cross-coupling term, which adds a feed-forward voltage to the q-axis voltage to compensate for the effect of the d-axis current:
\[u_d = -L_q i_q v_e\]Making this control mode more efficient and robust at higher velocities, at the cost of requiring the knowledge of the motor inductance.
To enable this mode provide the phase inductance values in the motor constructor or by setting the parameters directly:
motor.axis_inductance.q = L_q; // set q-axis inductance
See a deeper dive in motor dynamics and FOC control teory
Configuration & Tuning
To make this loop run smoothly, you must configure the PID controller for the q-axes. In current control, the I-gain is often much higher than in velocity or position loops because current reacts almost instantly.
| Parameter | Attribute | Typical Range | Description |
|---|---|---|---|
| P Gain | PID_current_q.P | $2.0 - 20.0$ | Immediate response to current error. |
| I Gain | PID_current_q.I | $300 - 5000$ | Eliminates steady-state error. Crucial for BEMF compensation. |
| Filter | LPF_current_q.Tf | $0.0001 - 0.01$ | Smooths noisy current readings. Lower is faster but noisier. |
// Example: Setting parameters for the Q axis
motor.PID_current_q.P = 5.0;
motor.PID_current_q.I = 1000.0;
motor.LPF_current_q.Tf = 0.005;
💡 Tip: Use Auto-Tuning
If you know your motor’s Phase Resistance (\(R\)) and Inductance (\(L\)), the library can calculate these PID gains for you! These two parameters are the “sweet spot” for performance—easy to measure and massive in their impact.
Learn about Parameter Measurement Learn about Current Auto-tuning
Targets, Limits and Feed-forward
The DC current torque control mode allows you to set the target current \(I_d\) directly, which is proportional to the torque generated by the motor. The current is specified in Amperes and is communicated to the torque control loop from the motion control loop through the current_sp variable of the motor object, for example:
motor.current_sp = 0.5; // set target current to 0.5 A
This variable is set internally in the FOCMotor object and should not be modified by the user. To control the torque of the motor, use the motion control mode torque and set the target variable:
motor.torque_controller = TorqueControlType::dc_current;
motor.controller = MotionControlType::torque;
motor.target = 0.5; // set target torque to 0.5 A
This current control mode allows the user to specify hard limits for current and voltage, which will be applied by the torque control loop. For example, to limit current to 1 A:
motor.current_limit = 1.0; // set current limit to 1 A
And to prevent the controller from applying excessive voltage:
motor.voltage_limit = 6.0; // set voltage limit to 6 V
The preferred way of setting these values is using the setter functions:
motor.updateCurrentLimit(1.0); // set current limit to 1 A
motor.updateVoltageLimit(6.0); // set voltage limit to 6 V
The library additionally allows the user to specify feed-forward voltage and current terms for the q-axis, which will be added to the control law. This is an advanced feature that can be used to improve performance in some applications, for example to compensate for gravity load in a robotic arm:
motor.feed_forward_voltage.q = 0.5; // set q-axis voltage feed-forward to 0.5 V
motor.feed_forward_voltage.d = 0.0; // set d-axis voltage feed-forward to 0 V
motor.feed_forward_current.q = 0.1; // set q-axis current feed-forward to 0.1 A
Torque control example code
BLDC motors Stepper motors Hybrid Stepper motors
A simple example of the DC current based torque control using Inline current sensor and setting the target value by serial command interface.
#include <SimpleFOC.h>
// BLDC motor & driver instance
BLDCMotor motor = BLDCMotor(11);
BLDCDriver3PWM driver = BLDCDriver3PWM(9, 5, 6, 8);
// encoder instance
Encoder encoder = Encoder(2, 3, 500);
// channel A and B callbacks
void doA(){encoder.handleA();}
void doB(){encoder.handleB();}
// current sensor
InlineCurrentSense current_sense = InlineCurrentSense(0.01, 50.0, A0, A2);
// instantiate the commander
Commander command = Commander(Serial);
void doTarget(char* cmd) { command.scalar(&motor.target, cmd); }
void setup() {
// initialize encoder sensor hardware
encoder.init();
encoder.enableInterrupts(doA, doB);
// link the motor to the sensor
motor.linkSensor(&encoder);
// driver config
// power supply voltage [V]
driver.voltage_power_supply = 12;
driver.init();
// link driver
motor.linkDriver(&driver);
// link the driver to the current sense
current_sense.linkDriver(&driver);
// current sense init hardware
current_sense.init();
// link the current sense to the motor
motor.linkCurrentSense(¤t_sense);
// set torque mode:
motor.torque_controller = TorqueControlType::dc_current;
// set motion control loop to be used
motor.controller = MotionControlType::torque;
// foc current control parameters
motor.PID_current_q.P = 5;
motor.PID_current_q.I= 300;
motor.LPF_current_q.Tf = 0.01;
// use monitoring with serial
Serial.begin(115200);
// comment out if not needed
motor.useMonitoring(Serial);
// initialize motor
motor.init();
// align sensor and start FOC
motor.initFOC();
// add target command T
command.add('T', doTarget, "target current");
Serial.println(F("Motor ready."));
Serial.println(F("Set the target current using serial terminal:"));
_delay(1000);
}
void loop() {
// main FOC algorithm function
motor.loopFOC();
// Motion control function
motor.move();
// user communication
command.run();
}