Hello!
I am currently struggling to achieved closed loop velocity control of a BLDC motor, and I due to my lack of experience I have not been able to figure out how to resolve my problem.
Here is the problem: upon attempting to run the motor, it stays stuck and cannot be moved by hand while heating up rapidly.
Here is the hardware I am using:
- Nucleo 64 stm32 G474RE
- SimpleFOC Drive v1.3
- 24V power supply (2 LiPo 12V batteries in series)
- BLDC 42BLS40-24-01 motor from StepperOnline (it has a very low internal resistance)
Here is my code:
#include <Arduino.h>
#include <SimpleFOC.h>
// ─────────────────────────────────────────────
// USER-TUNABLE PARAMETERS
// ─────────────────────────────────────────────
constexpr int POLE_PAIRS = 4; // 8 magnetic poles → 4 pole-pairs
constexpr float PHASE_RESISTANCE = 0.30f; // Ω (datasheet: ~0.3 Ω/phase)
constexpr float KV_SI = 260.0f * (float)(M_PI / 30.0); // ≈ 27.2 rad/s/V
constexpr float SUPPLY_VOLTAGE = 24.0f; // V
constexpr float CURRENT_LIMIT_A = 1.0f; // A (peak phase)
constexpr float TARGET_VELOCITY = 0.0f; // rad/s ≈ 478 RPM
// ── Velocity outer-loop PID ──────────────────
constexpr float VEL_P = 0.5f;
constexpr float VEL_I = 5.0f;
constexpr float VEL_D = 0.001f;
constexpr float VEL_TF = 0.010f;
constexpr float VEL_RAMP = 1000.0f;
// ── Q-axis (torque-producing) current inner-loop PID ────────────────────────
constexpr float CUR_Q_P = 5.0f;
constexpr float CUR_Q_I = 300.0f;
constexpr float CUR_Q_D = 0.0f;
constexpr float CUR_Q_LPF_TF = 0.002f; // 2 ms → ~80 Hz LPF on Iq feedback
// ── D-axis (flux) current inner-loop PID ────────────────────────────────────
constexpr float CUR_D_P = 5.0f;
constexpr float CUR_D_I = 300.0f;
constexpr float CUR_D_D = 0.0f;
constexpr float CUR_D_LPF_TF = 0.002f;
constexpr uint32_t MONITOR_DS = 12500;
// ─────────────────────────────────────────────
// HARDWARE OBJECTS
// ─────────────────────────────────────────────
BLDCDriver3PWM driver(
/* phA */ PA6, // D6
/* phB */ PB6, // D10
/* phC */ PB4, // D5
/* en */ PA9 // D8
);
InlineCurrentSense current_sense(0.001f, 22.222f, A0, A2);
HallSensor sensor(
/* H1 */ PA10, // D2
/* H2 */ PB3, // D3
/* H3 */ PB5, // D4
POLE_PAIRS
);
BLDCMotor motor(POLE_PAIRS, PHASE_RESISTANCE, KV_SI);
Commander command(Serial);
// ─────────────────────────────────────────────
// HALL INTERRUPT CALLBACKS
// ─────────────────────────────────────────────
void doH1() { sensor.handleA(); }
void doH2() { sensor.handleB(); }
void doH3() { sensor.handleC(); }
// ─────────────────────────────────────────────
// COMMANDER CALLBACKS
// ─────────────────────────────────────────────
void onMotor(char* cmd) { command.motor(&motor, cmd); }
void onTarget(char* cmd) { command.scalar(&motor.target, cmd); }
// ─────────────────────────────────────────────
// SETUP
// ─────────────────────────────────────────────
void setup() {
Serial.begin(115200);
delay(500);
Serial.println(F("=== BOOT ==="));
Serial.println(F("============================================"));
Serial.println(F(" 42BLS40 True-FOC Velocity Control"));
Serial.println(F(" SimpleFOC Drive v1.3 / Nucleo-G474RE"));
Serial.println(F(" Mode: 3-PWM + inline current sense"));
Serial.println(F("============================================"));
Serial.println();
// ── 1. Hall sensor ───────────────────────────
sensor.init();
sensor.enableInterrupts(doH1, doH2, doH3);
Serial.println(F("[OK] Hall sensor initialised"));
// ── 2. Gate driver ───────────────────────────
driver.voltage_power_supply = SUPPLY_VOLTAGE;
driver.voltage_limit = SUPPLY_VOLTAGE;
driver.pwm_frequency = 25000;
if (driver.init() != 1) {
Serial.println(F("[FATAL] Driver init failed – check PWM/EN wiring. Halting."));
while (1) {}
}
Serial.println(F("[OK] Driver initialised (3-PWM, 25 kHz)"));
// ── 3. Inline current sense ──────────────────
current_sense.linkDriver(&driver);
if (current_sense.init() != 1) {
Serial.println(F("[FATAL] Current sense init failed – check A0/A2 wiring. Halting."));
while (1) {}
}
// ── Polarity check ───────────────────────────
Serial.println(F("[OK] Current sense initialised (phases A+B, C reconstructed)"));
// ── 4. Motor configuration ───────────────────
motor.linkSensor(&sensor);
motor.linkDriver(&driver);
motor.linkCurrentSense(¤t_sense);
// Control topology
motor.controller = MotionControlType::velocity;
motor.torque_controller = TorqueControlType::foc_current;
// Velocity PID
motor.PID_velocity.P = VEL_P;
motor.PID_velocity.I = VEL_I;
motor.PID_velocity.D = VEL_D;
motor.PID_velocity.output_ramp = VEL_RAMP;
motor.LPF_velocity.Tf = VEL_TF;
// Q-axis current PID
motor.PID_current_q.P = CUR_Q_P;
motor.PID_current_q.I = CUR_Q_I;
motor.PID_current_q.D = CUR_Q_D;
motor.LPF_current_q.Tf = CUR_Q_LPF_TF;
// D-axis current PID
motor.PID_current_d.P = CUR_D_P;
motor.PID_current_d.I = CUR_D_I;
motor.PID_current_d.D = CUR_D_D;
motor.LPF_current_d.Tf = CUR_D_LPF_TF;
// Limits
motor.voltage_sensor_align = 0.5f;
motor.current_limit = CURRENT_LIMIT_A;
motor.voltage_limit = 0.5f;
motor.velocity_limit = 200.0f;
motor.sensor_direction = Direction::CW;
// ── 5. Monitoring ────────────────────────────
motor.useMonitoring(Serial);
motor.monitor_variables = _MON_TARGET | _MON_VEL | _MON_CURR_Q | _MON_CURR_D;
motor.monitor_downsample = MONITOR_DS;
// ── 6. Initialise and calibrate ──────────────
Serial.println(F("Calling motor.init()..."));
motor.init();
Serial.println(F("Calling motor.initFOC()..."));
motor.initFOC();
Serial.println(F("[OK] FOC initialised – motor ready"));
// ── 7. Set initial target ────────────────────
motor.target = TARGET_VELOCITY;
// ── 8. Commander ─────────────────────────────
command.add('M', onMotor, "motor (full param tree)");
command.add('T', onTarget, "target velocity [rad/s]");
Serial.println();
Serial.println(F("── Runtime commands ──────────────────────────"));
Serial.println(F(" T<value> set target velocity (rad/s)"));
Serial.println(F(" MVP<val> set velocity P gain"));
Serial.println(F(" MVI<val> set velocity I gain"));
Serial.println(F(" MCQP<val> set Q-axis current P gain"));
Serial.println(F(" MCQI<val> set Q-axis current I gain"));
Serial.println(F(" ML<val> set current limit (A)"));
Serial.println(F(" MS0 disable / coast"));
Serial.println(F(" MS3 open-loop velocity (diagnostics)"));
Serial.println(F(" M print full motor status"));
Serial.println(F("──────────────────────────────────────────────"));
Serial.println(F("Monitor columns: target[rad/s], vel[rad/s], Iq[A], Id[A]"));
Serial.println();
}
// ─────────────────────────────────────────────
// MAIN LOOP
// ─────────────────────────────────────────────
void loop() {
motor.loopFOC();
motor.move();
motor.monitor();
command.run();
}
Here is the output of my last test:
=== BOOT ===
42BLS40 True-FOC Velocity Control
SimpleFOC Drive v1.3 / Nucleo-G474RE
Mode: 3-PWM + inline current sense
[OK] Hall sensor initialised
[OK] Driver initialised (3-PWM, 25 kHz)
[OK] Current sense initialised (phases A+B, C reconstructed)
MOT:Monitor enabled!
Calling motor.init()…
MOT:Init
MOT:Enable driver.
Calling motor.initFOC()…
MOT:Align sensor.
MOT:Skip dir calib.
MOT:Zero elec. angle: 0.00
MOT:Align current sense.
MOT:Success: 1
MOT:Ready.
[OK] FOC initialised – motor ready
── Runtime commands ──────────────────────────
T set target velocity (rad/s)
MVP set velocity P gain
MVI set velocity I gain
MCQP set Q-axis current P gain
MCQI set Q-axis current I gain
ML set current limit (A)
MS0 disable / coast
MS3 open-loop velocity (diagnostics)
M print full motor status
──────────────────────────────────────────────
Monitor columns: target[rad/s], vel[rad/s], Iq[A], Id[A]
0.0000 8317.2216 2984.8823 0.0000
0.0000 7919.0249 2817.5078 0.0000
0.0000 7588.7368 2751.7604 0.0000
0.0000 7298.4711 2631.5268 0.0000
Here is a photo of my setup:
Any help is greatly appreciated! ![]()
