Position Control Problem With Hall Sensors for BLDC

Hello all! I have recently build a BLDC motor controller which works great. I have tested and implemented it with hall sensors and am able to get good performance with some velocity code. I am now trying to write the position code equivalent so that I will be able to implement “holds” where the bldc motor will stop moving and hold it’s position. This is because I plan to use it to build a robotic arm in which position holds will be needed. Is there any way to implement a position hold with the hall sensor velocity code? Setting velocity to 0 doesnt seem to work well as it jitters like crazy. I understand the position control code has support for this but it doesnt seem to work - it calibrates and moves to the hall sensors find but when it hits the main loop it stutters a ton not really moving. Any thoughts to the problem? Any help is appreciated!

This is the working hall sensor velocity code which I’ve tested to work:

#include <SimpleFOC.h>

// Define the BLDC motor pins
#define BLDC_PWM_UH_GPIO 12
#define BLDC_PWM_UL_GPIO 11
#define BLDC_PWM_VH_GPIO 10
#define BLDC_PWM_VL_GPIO 9
#define BLDC_PWM_WH_GPIO 8
#define BLDC_PWM_WL_GPIO 18

#define DRV0_CSN_PIN 48
#define DRV1_SCK_PIN 47

#define HALLU_PIN 6
#define HALLV_PIN 7
#define HALLW_PIN 15

#define ENABLE_PIN 38

#define POLE_PAIRS 2 // Number of permanent magnets in motor divided by 2

// Create BLDCMotor and BLDCDriver6PWM objects
BLDCMotor motor = BLDCMotor(POLE_PAIRS);  // number of pole pairs
BLDCDriver6PWM driver = BLDCDriver6PWM(BLDC_PWM_UH_GPIO, BLDC_PWM_UL_GPIO, BLDC_PWM_VH_GPIO, BLDC_PWM_VL_GPIO, BLDC_PWM_WH_GPIO, BLDC_PWM_WL_GPIO);

// hall sensor instance
HallSensor sensor = HallSensor(HALLU_PIN, HALLV_PIN, HALLW_PIN, POLE_PAIRS);

// Interrupt routine intialisation
// channel A and B callbacks
void doA(){sensor.handleA();}
void doB(){sensor.handleB();}
void doC(){sensor.handleC();}

// velocity set point variable
float target_velocity = -80;

unsigned long previousMillis = 0;  // will store last time LED was updated

// instantiate the commander
Commander command = Commander(Serial);
void doTarget(char* cmd) { command.scalar(&target_velocity, cmd); }

void setup() {

  pinMode(ENABLE_PIN, OUTPUT);
  pinMode(DRV0_CSN_PIN, OUTPUT);
  pinMode(DRV1_SCK_PIN, OUTPUT);

  digitalWrite(DRV1_SCK_PIN, LOW);
  digitalWrite(DRV0_CSN_PIN, HIGH);
  
  digitalWrite(ENABLE_PIN, LOW);
  delay(250);
  digitalWrite(ENABLE_PIN, HIGH);

  // use monitoring with serial 
  Serial.begin(115200);
  // enable more verbose output for debugging
  // comment out if not needed
  //SimpleFOCDebug::enable(&Serial);

  // initialize sensor sensor hardware
  sensor.init();
  sensor.enableInterrupts(doA, doB, doC);

  // link the motor to the sensor
  motor.linkSensor(&sensor);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 24;
  driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);

  // aligning voltage [V]
  motor.voltage_sensor_align = 3;

  // set motion control loop to be used
  motor.controller = MotionControlType::velocity;

  // contoller configuration
  // default parameters in defaults.h

  // velocity PI controller parameters
  motor.PID_velocity.P = 0.2f;
  motor.PID_velocity.I = 2;
  motor.PID_velocity.D = 0;
  // default voltage_power_supply
  motor.voltage_limit = 2;
  // jerk control using voltage voltage ramp
  // default value is 300 volts per sec  ~ 0.3V per millisecond
  motor.PID_velocity.output_ramp = 1000;

  // velocity low pass filtering time constant
  motor.LPF_velocity.Tf = 0.01f;

  // 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 voltage");

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


void loop() {
  /*unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 2100)
  {
    previousMillis = currentMillis;
    target_velocity *= -1;
  }*/

  // main FOC algorithm function
  // the faster you run this function the better
  // Arduino UNO loop  ~1kHz
  // Bluepill loop ~10kHz
  motor.loopFOC();

  // Motion control function
  // velocity, position or voltage (defined in motor.controller)
  // this function can be run at much lower frequency than loopFOC() function
  // You can also use motor.move() and set the motor.target in the code
  motor.move(target_velocity);

  // function intended to be used with serial plotter to monitor motor variables
  // significantly slowing the execution down!!!!
  // motor.monitor();

  // user communication
  command.run();
}

This is the position control code that I am trying to get working but has problems in the main loop. It just stutters and wont move to the position after calibrating the hall sensors:

#include <SimpleFOC.h>

// Define the BLDC motor pins
#define BLDC_PWM_UH_GPIO 12
#define BLDC_PWM_UL_GPIO 11
#define BLDC_PWM_VH_GPIO 10
#define BLDC_PWM_VL_GPIO 9
#define BLDC_PWM_WH_GPIO 8
#define BLDC_PWM_WL_GPIO 18

#define DRV0_CSN_PIN 48
#define DRV1_SCK_PIN 47

#define HALLU_PIN 6
#define HALLV_PIN 7
#define HALLW_PIN 15

#define ENABLE_PIN 38

#define POLE_PAIRS 2 // Number of permanent magnets in motor divided by 2

// BLDC motor & driver instance
BLDCMotor motor = BLDCMotor(POLE_PAIRS);
BLDCDriver6PWM driver = BLDCDriver6PWM(BLDC_PWM_UH_GPIO, BLDC_PWM_UL_GPIO, BLDC_PWM_VH_GPIO, BLDC_PWM_VL_GPIO, BLDC_PWM_WH_GPIO, BLDC_PWM_WL_GPIO);

// hall sensor instance
HallSensor sensor = HallSensor(HALLU_PIN, HALLV_PIN, HALLW_PIN, POLE_PAIRS);

// Interrupt routine intialisation
// channel A and B callbacks
void doA(){sensor.handleA();}
void doB(){sensor.handleB();}
void doC(){sensor.handleC();}

// angle set point variable
float target_angle = 2;
// instantiate the commander
Commander command = Commander(Serial);
void doTarget(char* cmd) { command.scalar(&target_angle, cmd); }

void setup() {
  
  pinMode(ENABLE_PIN, OUTPUT);
  pinMode(DRV0_CSN_PIN, OUTPUT);
  pinMode(DRV1_SCK_PIN, OUTPUT);

  digitalWrite(DRV1_SCK_PIN, LOW);
  digitalWrite(DRV0_CSN_PIN, HIGH);
  
  digitalWrite(ENABLE_PIN, LOW);
  delay(250);
  digitalWrite(ENABLE_PIN, HIGH);

  // initialize sensor hardware
  sensor.init();
  sensor.enableInterrupts(doA, doB, doC);

  // link the motor to the sensor
  motor.linkSensor(&sensor);

  // driver config
  // power supply voltage [V]
  driver.voltage_power_supply = 24;
  driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);


  // aligning voltage [V]
  motor.voltage_sensor_align = 3;
  // index search velocity [rad/s]
  motor.velocity_index_search = 3;

  // set motion control loop to be used
  motor.controller = MotionControlType::angle;

  // contoller configuration
  // default parameters in defaults.h

  // velocity PI controller parameters
  motor.PID_velocity.P = 0.2f;
  motor.PID_velocity.I = 2;
  motor.PID_velocity.D = 0;
  // default voltage_power_supply
  motor.voltage_limit = 2;
  // jerk control using voltage voltage ramp
  // default value is 300 volts per sec  ~ 0.3V per millisecond
  motor.PID_velocity.output_ramp = 1000;

  // velocity low pass filtering time constant
  motor.LPF_velocity.Tf = 0.01f;

  // angle P controller
  motor.P_angle.P = 100;
  //  maximal velocity of the position control
  motor.velocity_limit = 100;

  // 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 angle");

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

void loop() {
  // main FOC algorithm function
  // the faster you run this function the better
  // Arduino UNO loop  ~1kHz
  // Bluepill loop ~10kHz
  motor.loopFOC();

  // Motion control function
  // velocity, position or voltage (defined in motor.controller)
  // this function can be run at much lower frequency than loopFOC() function
  // You can also use motor.move() and set the motor.target in the code
  motor.move(target_angle);

  // function intended to be used with serial plotter to monitor motor variables
  // significantly slowing the execution down!!!!
  // motor.monitor();

  // user communication
  command.run();
}

Thank you for your time!

For one thing, your motor.Angle_P.P is probably too high. Usually around 20 is best.

Add this in your main loop before motor.move

  // Don't try to stop between cogging steps
  if (motor.controller == MotionControlType::angle) {
    float c = _PI_3/motor.pole_pairs;
    target_angle = floor(target_angle / c + 0.5f) * c;
  }

The HallSensor angle only changes in whole steps, so if the target is inbetween two of them, the motor will oscillate back and forth, never able to get the sensor exactly equal to target.

@dekutree64 This definitely helps! This got it moving but when it approaches the endpoint it doesnt come to a stop and just jitters around it. Any thoughts? Thanks so much for your help! – robosnake26 just now

Probably just more tuning needed. Keep reducing the angle P and see if it becomes stable or continues oscillating very slowly. And try adjusting the velocity values as well. Usually PID_Velocity.I should be about 10x PID_Velocity.P like you have it, but one of my motors has P=0.05 and I=1.0, so 20x difference.

@dekutree64 you’re right! I halved my P to .1 and it works fine now. thanks for your help!