Help needed for closed loop BLDC velocity control with SimpleFOC

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(&current_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! :smile:

7 Answers

7

Have you tried setting the target to something other than 0? Holding position is the expected behavior for commanding 0 velocity in open loop. If so, and it still won’t move, then try disconnecting the motor and checking the output voltages (relative to ground) with a multimeter. Use angle open loop mode and change the target angle and motor.voltage_limit and see if the measured voltages change accordingly.

Thank you for the reply! Yes the motor does not move regardless of the target velocity, I just set it to 0 in this case to check if the motor would still heat up rapidly if it was not asked to move, which it did :frowning: . I unfortunately do not have access to a multimeter at the moment, are there any other tests I could try to identify the source of the problem (whether it is the motor, wiring, or the code)?

Hey there,

The best idea might be to start with the getting started guide.
It’s simple and you should be able to pin point the main issues a bit quicker.

I have a similar motor (act 42BLF01) with almost the same numbers, except it has 2.2ohm phase to phase resistance. For simplefoc I have to set 1.1ohm (single phase)

It wouldn’t hurt to try the same number.

I think the main problem I’m facing is the fact that I cannot seem to read the information from the hall sensors, no matter what I try :frowning: .

I’ve been trying to get this to work with this motor for a while now and I think I might pivot to an entirely different set up, because I can not figure it out.

Then raise that value, 0.5 V is pretty low for a 24V motor.

I am not sure if that is the source of the problem, as I get no input from the hall sensors at all when using the code from the get started guide to test the sensor (https://docs.simplefoc.com/test_sensor), it simply stays at 0 for the angle and velocity.

I tried multiple different pins, to no avail.