Help me figure out what I'm doing wrong, please

I have an RCTimer 5010 360kV BLDC motor and a B_G431B_ESC which works with the ST MC SDK reasonably well. The motor is a 12N14P, and verified that with motor.velocity=6.28 it does turn once per second.

I measured 0.28 Ohm between each phase, and I’m using a 12V 5A power supply set up with a 2.5A protection current. The motor uses ~300mA with the ST MC SDK when running between 500 and 4000 rpm.

With the below code, SimpleFOC only works up to a motor.velocity of 32, any higher and it makes audible noise

I’m following the Getting started | Arduino-FOC and stuck on step 2 :frowning: what else can I try? I checked that the PWM driver are properly initialized, and I used the BLDCDriver6PWM() definition from the G431 ESC example

#include <Arduino.h>
#include <SimpleFOC.h>

BLDCMotor motor = BLDCMotor(7);
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);

Commander command = Commander(Serial);
void doTarget(char *cmd) { command.scalar(&motor.target, cmd); }
void doLimit(char *cmd) { command.scalar(&motor.voltage_limit, cmd); }

void setup()
{
driver.voltage_power_supply = 12;
driver.init();
motor.linkDriver(&driver);

motor.voltage_limit = 1;  // [V] - if phase resistance not defined
motor.velocity_limit = 5000;
motor.target = 20;

motor.controller = MotionControlType::velocity_openloop;

motor.init();

command.add('T', doTarget, "target velocity");
command.add('L', doLimit, "voltage limit");

Serial.begin(115200);
Serial.println(F("Motor ready."));
_delay(1000);
}

void loop()
{
  motor.move();

  command.run();
}

I tried multiple values of V, adding or removing the motor.phase_resistance value of 0.28, current limits, etc, but I can’t seem to find a combination that works for speeds higher than ~30-40

I think voltage limit is the problem. 360kv*1V is 360RPM, which is 6 revolutions per second, which is 37.7 radians per second. Not surprising that open loop starts having trouble above 30.

Thanks for pointing me in the right direction, @dekutree64. I did actually try to change the voltage from 0.5V to 12V, but it never truly worked and the PSU protection kicked in.

After your comment, I finally started increasing the speed by 5 each time, and increasing voltage by 0.2V, keeping an eye on the PSU power delivery. And I finally noticed how quickly the current increased as soon as I changed the voltage limit even a bit. So all I had to do was to increase the voltage limit, see the current spike to, say, 800mA, increase velocity until the power consumption dropped to ~150mA, and repeat, If I increased the voltage limit too quickly, the PSU protection kicks in. If I increase the velocity too much, the motor stops. Before now, I never had the magic combination.

The only way to increase velocity is to slowly increase both velocity and voltage. Which in hindsight it makes sense, I guess, given the low phase resistance of my motor.

Yeah, the open loop modes aren’t good for much aside from testing. You can prevent the overload by giving it the phase resistance and kv so it can use the voltage-based current limiting, but it will still apply the full current limit even if it doesn’t need to.

It would be possible to make a sort of closed loop sensorless mode that tries to find the minimum current necessary to maintain the target speed. Anthony wrote some it here Concept for a super simple yet reasonably good sensorless driver with SimpleFOC but it would need hardware with BEMF sensing ability to know what the current RPM is, which is usually not included on boards designed for sensored control (although it probably is on B-G431B-ESC1 since it’s designed for educational use).

Actually I do have an encoder, but I wanted to understand how open_loop worked before moving on. I just didn’t appreciate how small the margin of error was in trying to hit the right combination of voltage limit and velocity. In order to get to, e.g. 200, I need to increase voltage and velocity a bit at a time in probably 15 steps.

If I jump ahead and set up, say, 4V and 200 directly, the motor locks and the PSU goes into protection, dropping the voltage. I need to slowly increase velocity and voltage so that the motor keeps accelerating smoothly without drawing too much power

Thanks again @dekutree64, this helped a lot!

Note that if you close the loop the current consumption will be much lower.

Then you can also provide the phase resistance to the constructor of BLDCMotor to have it control the estimated current rather than the voltage, or use the B-G431-ESC1’s current sensing to control the actual current.

I’m stuck again :frowning:

I started using FOC and the encoder, with what I think it’s the B431 ESC current sensing, but can only get up to velocity=175 before it starts drawing too much current and triggering the PSU protection. When that happens, even if I lower the velocity, it never recovers and I have to cut power.

With velocity=175, I can feel that the motor is running pretty rough, but drawing only ~200mA. at 100 it runs pretty smooth. When I try to increase T to 200 form 175, it draws too much current (>2.5A) and the voltage drops, causing all sorts of problems to the algorithm.

Code below, in case you see something obviously wrong. Where do I start optimizing? I don’t expect anyone to figure out everything for me, just pointers to get started. I tried changing slightly P and I, but that made no difference, as changing voltage limit made no difference. PSU is 12V and current limit 2.5A, so the 0.5A current limit should be well within tolerance.

#include <Arduino.h>
#include <SimpleFOC.h>

// Motor instance
BLDCMotor motor = BLDCMotor(7);
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003f, -64.0f / 7.0f, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);

// encoder instance
Encoder encoder = Encoder(A_HALL1, A_HALL2, 1024);

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

// instantiate the commander
Commander command = Commander(Serial);
void doTarget(char *cmd) { command.scalar(&motor.target, cmd); }
void doLimit(char *cmd) { command.scalar(&motor.voltage_limit, cmd); }
void onVelocity(char *cmd) { command.scalar(&motor.shaft_velocity, cmd); 

void setup()
{
  // initialize encoder sensor hardware
  encoder.init();
  encoder.enableInterrupts(doA, doB);

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

  // driver config
  driver.voltage_power_supply = 12;
  driver.init();
  // link the motor and the driver
  motor.linkDriver(&driver);
  // link current sense and the driver
  currentSense.linkDriver(&driver);

  // current sensing
  currentSense.init();
  // no need for aligning
  currentSense.skip_align = true;
  motor.linkCurrentSense(&currentSense);

  motor.current_limit = .5;
  motor.phase_resistance = 0.28;

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

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

  // velocity PI controller parameters
  motor.PID_velocity.P = 0.2;
  motor.PID_velocity.I = 20;
  motor.voltage_limit = 6;
  // 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.01;

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

  motor.target = 20;
  motor.torque_controller = foc_current;

  // use monitoring with serial
  Serial.begin(115200);
  // comment out if not needed
  motor.useMonitoring(Serial);

  // initialize motor
  motor.init();
  // align encoder and start FOC
  motor.initFOC();
// add target command T
command.add('T', doTarget, "target velocity");
command.add('L', doLimit, "voltage limit");
command.add('S', onVelocity, "speed");

Serial.println(F("Motor ready."));
_delay(1000);
}

void loop()
{
// main FOC algorithm function
motor.loopFOC();

// Motion control function
motor.move();

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

// user communication
command.run();
}

You could try setting the phase resistance on the motor. Target and limits will then be amps, perhaps the control will work better that way to stay within the PSU limits?

I’m sorry, I must be missing something. I thought I did it with these two lines

  motor.current_limit = .5;
  motor.phase_resistance = 0.28;

I set the PSU at 2.5A to prevent damage to the motor (it can take a lot more, but without a propeller it would overheat fast: it’s originally designed as a drone motor).

My board is drawing much more than 2.5A even with the phase and current limit set. That tells me something is not working as expected. Or did I misunderstand how that should work?

No, I missed it - normally you can set the phase resistance in the constructor, and I just noted you don’t have it there.

Not sure what is going on then…

I start suspecting the processor is too slow for velocity higher than 175 with the current configuration.

I tried recompiling with -Ofast, and I can run it at velocity = 250 before problems start. It’s weird because the STM32G432 should be plenty fast, but maybe there’s too much overhead in the code. That same ESC runs without problems at any speed with the ST MC SDK, so I need to understand where it spends the time

I have been using the same board… and a lot of open loop stuff. I definitely took an ill advised approach and it’s definitely a better idea to keep everything on one board, but I was having major problems with noise and glitching, so I started with the use of open loop, completely smooth as possible motion, and then built upon that. Priorities… And that wasn’t all wrong because there are a couple of noise sources built into the library that I would have had a very hard time plucking out and solving, and everything is a lot harder when everything interacts and can produce noise…

You can simplify the code and use it for testing. You could maybe use the power cap to cut the motor power if it stalls, which causes the current to jump, aiding testing.

I found it useful to watch things and adjust the power supply, however my motor is 12 ohms and the margin between optimal operation (1.5 radians angle between rotor and magnetic field) and stall is only like 0.2 volts. I don’t think you will be able to do much open loop except testing.

#include <SimpleFOC.h>

// NUMBER OF POLE PAIRS, NOT POLES, specific to the motor being used!
BLDCMotor motor = BLDCMotor(7); 
//this line must be changed for each board
BLDCDriver6PWM driver = BLDCDriver6PWM(A_PHASE_UH, A_PHASE_UL, A_PHASE_VH, A_PHASE_VL, A_PHASE_WH, A_PHASE_WL);
LowsideCurrentSense currentSense = LowsideCurrentSense(0.003, -64.0/7.0, A_OP1_OUT, A_OP2_OUT, A_OP3_OUT);
LowPassFilter diff_filter = LowPassFilter(0.05);
float goal_speed =0;
float v=2;
float v_diff=1;
float accel = 92;// in rads per second per second
float v_per_radsPS = 0.0232;
float accel_v_boost = 0.5;// voltage is increased during acceleration and deacceleration by this amount
bool voltage_override = 1;
float power_figure = 1.5;
float power_coeff = 0.000465;
float A, B, C;
float currentlf_now =0;
float prop_V= 0;
float min_V = 1;
float v_limit = 19;
float power_cap = 15;// this is just an estimate
void SerialComm(){ 
  float maybe_o = 1;
  if (Serial.available() > 0){
  switch(Serial.peek()){
      case 't': Serial.read(); Serial.print("t"); Serial.println(goal_speed); break;
      case 'c': Serial.read(); Serial.print("c"); Serial.println(accel); break;
      case 'v': Serial.read(); Serial.print("v"); Serial.println(motor.voltage_limit); break;
      case 'n': Serial.read(); Serial.print("n"); Serial.println(v_diff); break;
      case 'p': Serial.read(); Serial.print("p"); Serial.println(v_per_radsPS, 4); break;
      case 'b': Serial.read(); Serial.print("b"); Serial.println(accel_v_boost); break;
      case 'o': Serial.read(); Serial.print("o"); Serial.println(voltage_override); break;
      case 's': Serial.read(); Serial.print("s"); Serial.println(motor.target); break;
      case 'f': Serial.read(); Serial.print("f"); Serial.println(power_coeff, 6); break;
      case 'g': Serial.read(); Serial.print("g"); Serial.println(currentSense.getDCCurrent()); break;
      case 'i': Serial.read(); Serial.print("i"); Serial.println(get_mA()); break;
      case 'j': Serial.read(); Serial.print("j"); Serial.println(min_V); break;
      case 'w': Serial.read(); Serial.print("w"); Serial.println(driver.voltage_power_supply); break;
      case 'k': Serial.read(); Serial.print("k"); Serial.println(v_limit); break;
      
      case 'r': Serial.read(); Serial.print("r"); Serial.println(power_cap); break;
      case 'e': Serial.read(); Serial.print("e"); if (motor.shaft_angle >= 0){
           Serial.println(motor.shaft_angle, 3);
           }
           if (motor.shaft_angle < 0){
           Serial.println((_2PI-(-1*motor.shaft_angle)), 3);
           }
           break;
  case 'T': break;
  case 'C': break;
  case 'V':  break;
  case 'P':  break;
  case 'B':  break;
  case 'R':  break;
  case 'O': break;
  case 'F':  break;
  case 'J':  break;
  case 'W': ;break;
  case 'K': ;break;
  default: Serial.read(); break; //if anything we don't recognize got in the buffer, clear it out or it will mess things up.
       
  }
}
  if (Serial.available() >= 9){
  switch(Serial.read())
  { 
    
  case 'T': goal_speed = Serial.parseFloat();break;
  case 'C': accel = Serial.parseFloat();break;
  case 'V': v_diff = Serial.parseFloat(); break;
  case 'P': v_per_radsPS = Serial.parseFloat(); break;
  case 'K': v_limit = Serial.parseFloat(); break;// this doesn't work for some crazy reason.
  case 'B': accel_v_boost = Serial.parseFloat(); break;
  case 'R': power_cap = Serial.parseFloat(); break;
  case 'O': 
    maybe_o = Serial.parseFloat(); // just in case the wrong data gets in somehow we don't want the voltage going crazy
    if (maybe_o < 1){
      voltage_override = 0;
      } 
    if (maybe_o >= 0.999){ 
      voltage_override = 1;
    }
    break;// if it's not one of these, ignore it.
  case 'F': power_coeff = Serial.parseFloat();break;
  case 'W': driver.voltage_power_supply = Serial.parseFloat();break;
  case 'J': min_V = Serial.parseFloat();break;
  
  }
  }
}
void waste_heat_cap(){// if it stalls this won't help except at higher powers, probably.
  float power = driver.voltage_power_supply*get_mA();
  float mech_power = 0.00045*fabs(pow(motor.target/10, 3));

  float normal_heat = power-mech_power*0.3;
  if (normal_heat > power_cap){

    voltage_override = 0;
  }
}

void setup() {
  Serial.begin(1000000);
  Serial.println("test serial2");
  // driver config
  // power supply voltage [V]
  
  driver.voltage_power_supply = 24;
  driver.init();

  // link the motor and the driver
  motor.linkDriver(&driver);
  currentSense.linkDriver(&driver);
  currentSense.init();
  currentSense.skip_align = true;
  FOCModulationType::SinePWM;
  motor.voltage_limit = 1;   // [V]
  motor.velocity_limit = 320; // [rad/s]
 
  motor.controller = MotionControlType::velocity_openloop;

  // init motor hardware
  motor.init();
  motor.voltage_limit = 2;
  goal_speed = 2;
}

unsigned long int ticks_diff(unsigned long int t2,unsigned long int t1){ //t2 should be after t1, this is for calculating clock times.
  if (t2<t1){//t2 must have wrapped around after t1 was taken
     return (4294967295-(t1-t2));
  }
     return (t2-t1);
} 
float get_mA(){
   float x =0;
   x = currentlf_now*motor.voltage_limit/24;
   return  (4.0384440932900223e-002)+3.4514090071108776e-002*x*30;// this is off by like 12 percent in some cases a polynomial of third order fits the data better but might flake out at higher than 500 mA so I didn't try it.
}
void loop() {
     static unsigned long int loop_clock_in = millis();
     unsigned long int loop_time = 0;
     float loop_time_s = 0;
//     unsigned long int inner_loop_time = 0;
     loop_time = ticks_diff(millis(), loop_clock_in);
     loop_clock_in=millis();
     loop_time_s = float(loop_time)/1000;
     if (motor.target < goal_speed-(accel*loop_time_s*1.5)){//commutation speed not positive enough
           if (motor.target < 0){//counterclockwise rotation, deaccelerating
      motor.target = motor.target+accel*loop_time_s*0.7;
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }
          if (motor.target >= 0){ //clockwise rotation, accelerating
      motor.target = motor.target+accel*loop_time_s;
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }
     }
     
     if (motor.target>=goal_speed-(accel*loop_time_s*1.5)){//steady run phase
      if (motor.target<=goal_speed+(accel*loop_time_s*1.5)){ 
        motor.move();
      prop_V = (v_diff+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override; //constant run
      }
     }
     
     
     if (motor.target > goal_speed + (accel*loop_time_s*1.5)){ //commutation speed too positive
           if (motor.target > 0){ //clockwise rotation, deaccelerating
      motor.target = motor.target-accel*loop_time_s*0.7; 
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     } 
          if (motor.target <= 0){
      motor.target = motor.target-accel*loop_time_s; //counterclockwise rotation, accelerating
      motor.move();
      prop_V = (v_diff+accel_v_boost+fabs((motor.target*v_per_radsPS))+(power_coeff*pow(fabs(motor.target),power_figure)))*voltage_override;
     }

     }
     if (prop_V < min_V){
      motor.voltage_limit = min_V*voltage_override;
     }
     else {
      motor.voltage_limit = prop_V;
     }
          if (prop_V > v_limit){
      motor.voltage_limit = v_limit;
     }
     
     for (int i=0;i<10;i++){ // shouldloop at about 37 khz on b-g431 board, appears to be only 5khz on lepton
     for (int q=0;q<5;q++){
     motor.move();
     motor.move();
     motor.move();
     motor.move();
     motor.move();
     }     
//     Serial.println(micros()-inner_loop_time);
//     inner_loop_time = micros();
     currentlf_now = currentSense.getDCCurrent();
     currentlf_now = diff_filter(currentlf_now);
     SerialComm();
    
     }
     
     waste_heat_cap();

}

Deku:
My idea for the use of current to implement sensorless operation actually does not require back emf detection, you let the physics do that for you :). Only total net current drawn from power supply, which could be a single shunt or sensor on one of the inputs to the h-bridge. Optimal drive occurs at the same point as minimal current draw, that’s the key. As long as things are fast enough, I think it could work.

The code above implements acceleration and boosts the voltage. The acceleration during deacceleration for some reason is different for my motor, that’s what the magic number 0.7 is for, you could get rid of that. Sorry it’s not very well written, so much to do, no time.

Actually there are a lot of details there and it might be kind of hard to use… you have to send exactly 9 characters to set parameters, ideally. Just pad everything with enough zeros, if you add enough it works, you add some extras with no problem, they will just get cleared out and ignored. I did this because without it it can read the serial port in the middle of a transmission and misinterpret sometimes.

There is plenty of processing power to go more than 250 rads, I had no problem with that part. It runs the motor.move in isolation at 37.5 khz, so you can estimate maximal rads/second from that.

2 Likes

Hi @robca , please let me know if you were able to solve this issue.
I’m running into a similar problem. The motor draws too much current for higher speeds (more than it’s rated capacity). Further, by limiting the voltage or current(using phase resistance) is limiting it’s speed to less than 1/10th of it’s rated capacity.
Not sure where to start optimizing this.

I tried recompiling with -Ofast, and I can run it at velocity = 250 before problems start.

What is -Ofast? pardon my ignorance.

Haven’t had time to look into this yet. I suspect that there is a portion of inefficient code in some of the most timing-critical portions of the code (STM32G4 code, I mean), that slows down the loop too much. Possibly in the current sensing code, which is very G4-specific. The STM HAL can, in some cases, be horribly slow.

-Ofast is a compile optimization flag that improves speed at the cost of memory (i.e. code is compiled to be as fast as possible even if that means expanding the memory used). Usually Arduino code is compiled with -Os, which improves speed but not as much as -Ofast. in Platformio you add the below to the project platformio.ini file (remove the default -Os flag, ass -Ofast). Not sure how to do it with Arduino IDE.

build_unflags = -Os
build_flags = -Ofast

I have unfortunately found out the hard way that there are a lot of things that can lead to this. I had a very similar problem using a b-g431 board, in terms of symptoms, but having learned more about motor drive I can say that there are a number of things that can lead to this… for example, the angular error imposed on the sensor reading is considerable and has several sources that add up, this can easily lead to a mismatch between the magentic field angle and the rotor. One of the error sources is the time to read the sensor, another is code execution. You could try applying a linear compensation term of X*RPM to the sensor angle, don’t forget to do it before the fmod or it could exceed 2pi at some point, and never reach zero…

Quick update on the closed loop issue I was having. The problem for me was using the interrupt-driven AB encoder. In the beginning I was using the STM32HW encoder, but that code had problems (since fixed by @runger).

The STM32 code uses the HAL abstraction layer, which is notoriously bad for real time handling. Add to that the Arduino abstractions, and the code was spending way too much time processing the pulses from the AB encoder, causing problems. Had I used a SPI encoder, I would not have seen this

I’m now using the STM32HW encoder (the version in the DEV branch), and I can spin the motor at ~3800 rpm, basically the same speed as the STM Motor SDK. The encoder pulses are now processed entirely in hardware (minus a tiny amount of processing to transform from pulses to radians), and the code works as expected.

Even a fast G431 can get bogged down by horribly inefficient code called tens of thousands of times per second from interrupts

2 Likes

Man, so many possible things to go wrong, so hard to fix. Glad you got it working, but let’s face it takes considerable skill and time to do that. To pull this off for every issue on every chip board and sensor combo is combinatorially prohibitive. This is why I am a fan of getting things really working on at least one board that’s good for a fairly broad range of things, and so it is good to see things coalescing around the g431 chip, hopefully I can help make a board with it soon.