Link
On this page

Torque control using voltage

This torque control approach allows you to run the BLDC and Stepper motor as it is simple DC motor, where you set the target voltage \(u_q\) to be set to the motor and the FOC algorithm calculates the necessary phase voltages \(u_a\) ,\(u_b\) and \(u_c\) for smooth operation. This mode is enabled by:

// voltage torque control mode
motor.torque_controller = TorqueControlType::voltage;

How does it work?

Choose the motor type:

BLDC motors Stepper motors Hybrid Stepper motors

The voltage control algorithm reads the angle \(a\) from the position sensor and the gets target \(u_q\) voltage value from the user and using the FOC algorithm sets the appropriate \(u_a\), \(u_b\) and \(u_c\) voltages to the motor. FOC algorithm ensures that these voltages generate the magnetic force in the motor rotor exactly with 90 degree offset from its permanent magnetic field, which guarantees maximal torque, this is called commutation.

The assumption of the pure voltage control is that the torque generated (which is proportional to the current \(\tau = K_t i_q\)) in the motor is proportional the voltage as \(u_q\) set by the user. Maximal torque corresponds to the maximal \(u_q\) which is conditioned by the power supply voltage available, and the minimal torque is of course for \(u_q= 0\).

\[\tau \propto i_q \propto u_q\]

This equation is a rough approximation. In practice it is true only for low speeds, where the motors Back EMF voltage is negligible. As the motor speed increases, the Back EMF voltage generated by the motor will reduce the voltage set to the motor and therefore the current and torque generated will be lower than expected. Therefore in practice the voltage is proportional to the torque at low speeds, while it is proportional to the motor velocity at high speeds.

🔍 Where does this proportionality come from?

The motor electical equation (in static conditions - ex. constant speed ) has a form of:

\[u_q = i_q R + K_{e} v\]

where the \(R\) is the phase resistance of the motor, \(K_{e}\) is the back-emf constant of the motor and \(v\) is the motor velocity. If we assume that the current \(i_q\) is proportional to the torque generated \(\tau\) as \(K_t i_q =\tau\) (\(K_t\) is the torque constant), we can rewrite the equation as

\[\tau = \frac{K_t}{R} u_q - \frac{K_t K_{e}}{R} v\]

So in this equation we can see the when the motor is not moving (v=0) the voltage is proportional to the torque (through the \(\frac{K_t}{R}\) constant) but as the motor starts moving the back-emf voltage generated by the motor reduces the voltage set to the motor, and therefore the torque generated is lower than expected.

If we imagine that in an ideal conditions, where the motor turns without any load, the torque necesary to maintain the motor velocity is \(\tau=0\), then the equation can be rewritten as

\[u_q = K_{e} v\]

This means that in the ideal conditions the voltage set to the motor is proportional to the motor velocity, and therefore the motor behaves as if it is a simple DC motor where you set the voltage and the velocity reached is proportional to the voltage set.

In practice, these ideal conditions are never met, but the behavior of the motor is similar to the one described above, where at low speeds the voltage is proportional to the torque, while at high speeds it is proportional to the velocity.

Because Voltage Mode does not account for this velocity-dependent drop, the torque will always fade as you go faster. This is exactly what the Estimated Current Mode aims to fix.

⚠️ Practical limitations

This torque control approach is the fastest and most simple one to setup by it does not limit the current in any way!

If you can find motor parameters (phase resistance and the KV rating) using estimated current mode is recommended.

Learn more about estimated current mode

Expected motor behavior

If the user sets the desired voltage \(u_q\) of 0 Volts, the motor should not move and have some resistance, not too much, but more than when the motor is disconnected from the driver.

If you set a certain desired voltage \(u_q\) your motor should start moving and the velocity reached should be proportional to the voltage set \(u_q\). The behavior should be very similar to the DC motor controlled by changing the voltage on its wires.

Configuration and Torque Limits

To ensure safe operation and prevent hardware damage, you must configure the limits of your motor and driver. In Voltage Mode, your primary safety tool is motor.voltage_limit.

// a setter function to set the voltage limit
motor.updateVoltageLimit(2.0); 
// or you can set it directly as a variable (not recommended)
// [V] - set the maximum voltage allowed
motor.voltage_limit = 2.0; 

Choosing your voltage limit

Since Voltage Mode does not sense current, it cannot “know” if the motor is pulling too much power. A common mistake is setting the limit to the power supply voltage (e.g., 12V), which can easily fry a low-resistance motor.

The Rule of Thumb:

  • Gimbal Motors (\(R>5\Omega\)): You can safely set the limit to 30–50% of your power supply.
  • Drone Motors (\(R<1\Omega\)): Start very low (0.5V to 1.5V). Even a small voltage on these motors creates massive current.
    ⚠️ Don't be fooled by the motor size! A small drone motor can draw 10A at 2V, which can easily damage your driver and power supply if not limited.
  • Stepper Motors: (\(R \approx 2\Omega\)): Start with 2–5V and adjust based on performance and heat. Steppers can often handle higher voltages due to their construction, but they do have lower resistance than gimbal motors, so be cautious.
  • Heat Check: If the motor or driver becomes too hot to touch after 30 seconds of operation, decrease the motor.voltage_limit immediately.

Understanding the Voltage-Torque-Velocity relationship

In this mode, the voltage you set is split between overcoming the motor’s internal resistance (to create torque) and overcoming the Back-EMF (the “generator” effect of the spinning motor).

State Equation Result
Motor Stalled/Static \(u_q = i_q \cdot R\) Maximum torque is produced. All voltage goes into current.
Motor Spinning \(u_q = i_q \cdot R + K_{e}\cdot v\) Torque drops because \(u_q\) is now shared with the BEMF (proportional to speed \(v\)).
At Max Speed \(u_q \approx K_{e}\cdot v\) Current (\(i_q\)) drops to near zero. No torque is left to accelerate.

đź’ˇ Tip

If you find that your motor is too weak at high speeds, you have reached the BEMF limit of your current voltage_limit. To fix this, you would either need to increase the limit (carefully) or switch to Estimated Current Mode, which automatically increases voltage to compensate for Back-EMF.


See here for a dive deep into the torque theory. Go here for the implementation details.

Torque control example code

A simple example of the voltage based torque control and setting the target voltage by serial command interface.

BLDC motors Stepper motors Hybrid Stepper motors

#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();}

// instantiate the commander
Commander command = Commander(Serial);
void doMotor(char* cmd) { command.motor(&motor, 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);

  // set the torque control type
  motor.torque_controller = TorqueControlType::voltage;
  // set motion control loop to be used
  motor.controller = MotionControlType::torque;

  // 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 M
  command.add('M', doMotor, "motor");

  Serial.println(F("Motor ready."));
  Serial.println(F("Set the target voltage using serial terminal:"));
  _delay(1000);
}

void loop() {

  // main FOC algorithm function
  motor.loopFOC();

  // Motion control function
  motor.move();

  // user communication
  command.run();
}
#include <SimpleFOC.h>

// Stepper motor & driver instance
StepperMotor motor = StepperMotor(50); // nema17 200 steps per revolution
StepperDriver2PWM driver = StepperDriver2PWM(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();}

// instantiate the commander
Commander command = Commander(Serial);
void doMotor(char* cmd) { command.motor(&motor, 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);

  // set the torque control type
  motor.torque_controller = TorqueControlType::voltage;
  // set motion control loop to be used
  motor.controller = MotionControlType::torque;

  // 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 M
  command.add('M', doMotor, "motor");

  Serial.println(F("Motor ready."));
  Serial.println(F("Set the target voltage using serial terminal:"));
  _delay(1000);
}

void loop() {

  // main FOC algorithm function
  motor.loopFOC();

  // Motion control function
  motor.move();

  // user communication
  command.run();
}
#include <SimpleFOC.h>

// Stepper motor & driver instance
HybridStepperMotor motor = HybridStepperMotor(50); // nema17 200 steps per revolution
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();}

// instantiate the commander
Commander command = Commander(Serial);
void doMotor(char* cmd) { command.motor(&motor, 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);

  // set the torque control type
  motor.torque_controller = TorqueControlType::voltage;
  // set motion control loop to be used
  motor.controller = MotionControlType::torque;

  // 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 M
  command.add('M', doMotor, "motor");

  Serial.println(F("Motor ready."));
  Serial.println(F("Set the target voltage using serial terminal:"));
  _delay(1000);
}

void loop() {

  // main FOC algorithm function
  motor.loopFOC();

  // Motion control function
  motor.move();

  // user communication
  command.run();
}