Motor.disable() not functioning well with InlineCurrentSense() in foc_current

Hello everyone
When in foc_current mode with InlineCurrentSense() i am controlling a bldc motor setting the target_velocity. I wanted to achieve freewheeling when setting the target_velocity to 0, so i am using the motor.disable() function for it.

While observing the d and q currents in the serial plotter during testing, I noticed that when calling the motor.disable() the current sensing stops completely. Also, it seems that when the current sensing stops, the code saves up the last known current value and remembers it. After that, when restarting the rotation, the motor enables and the current sensing restarts. But for a brief moment after the startup, the driver still remembers the last meassured current value, that was saved up when the current sensing stoped, and uses this value for control. This value is obviously different than the actual current that flows at the startup. This makes the driver and the motor go crazy, and causes a huge overshoot of curent, either positive or negative, as the PID adjust for the actual meassured current. No PID tampering made any difference with this.

The problem exists when the motor.disable() is called at a higher velocity, when the difference between the last meassured current and the actual current is big. When velocity is lower while disableing, the difference between the last meassured and the actual current during startup is smaller, and the problem is less noticable or nonexistent.

I am not sure if that’s the way the library is set up or the problem is on my side, but i dont thinks it’s my code. I ran the same tests while using voltage mode with velocity control, and did not have this problem. Velocity sensing still continues after the motor.disable(), and the driver knows exacly what’s the actual motor velocity and regulates the motor smoothly to the newly set target.

Is there a remedy for this problem, or motor.disable() just can’t be used with current sensing?
Thanks in advance for any help.

My code:

#include <SimpleFOC.h>
#include <SimpleFOCDrivers.h>
#include <encoders/smoothing/SmoothingSensor.h>

#define   _MON_TARGET 0b1000000  // monitor target value
#define   _MON_VOLT_Q 0b0100000  // monitor voltage q value
#define   _MON_VOLT_D 0b0010000  // monitor voltage d value
#define   _MON_CURR_Q 0b0001000  // monitor current q value - if measured
#define   _MON_CURR_D 0b0000100  // monitor current d value - if measured
#define   _MON_VEL    0b0000010  // monitor velocity value
#define   _MON_ANGLE  0b0000001  // monitor angle value

float target_velocity;
int analog_read_A0;
float analog_read_A0_filtered;

BLDCMotor motor = BLDCMotor(15);

HallSensor sensor =  HallSensor(PB7, PA15, PB6, 15);
SmoothingSensor smooth(sensor, motor);

InlineCurrentSense current_sense  = InlineCurrentSense(44.0, PA4, PA5, PA6);

BLDCDriver6PWM driver = BLDCDriver6PWM(PA8, PA11, PA10, PF0, PA9, PA12);

Commander command = Commander(Serial);
LowPassFilter filter = LowPassFilter(0.002);

void onTarget(char* cmd) {command.target(&motor, cmd);}
void onPid(char* cmd) {command.pid(&motor.PID_current_d, cmd);}
void onPid1(char* cmd) {command.pid(&motor.PID_current_q, cmd);}
//void onMotor(char* cmd)  { command.motor(&motor,cmd);}

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

void enableSmoothing(char* cmd) {
  float enable;
  command.scalar(&enable, cmd);
  motor.linkSensor(enable == 0 ? (Sensor*)&sensor : (Sensor*)&smooth);
}



void setup() {

  pinMode(PA0, INPUT);
  pinMode(PA1, INPUT);
  pinMode(PA7, OUTPUT);
  digitalWrite(PA7, HIGH);

  Serial.begin(2000000);
  
  analogReadResolution(12);

  motor.useMonitoring(Serial);
  //SimpleFOCDebug::enable(NULL);
 
  sensor.pullup = Pullup::USE_EXTERN;
  
  sensor.init();
 
  sensor.enableInterrupts(doA, doB, doC);
  sensor.velocity_max = 61;  
  smooth.phase_correction = -_PI_6;
  
  motor.linkSensor(&smooth);


  driver.voltage_power_supply = 32;
  driver.pwm_frequency = 45000;
  //driver.dead_zone = 0.005;
  //driver.enable_active_high=true;
  
  Serial.print("Driver init ");
  if (driver.init()) 
  Serial.println("success!");
  else{Serial.println("failed!");
  return;}
  motor.linkDriver(&driver);
  
  current_sense.linkDriver(&driver);
  current_sense.init();  
  motor.linkCurrentSense(&current_sense); 
  
  motor.voltage_sensor_align = 1;

  motor.motion_downsample = 4;  
  
  motor.velocity_limit = 61; 
  motor.phase_resistance = 0.395; // [Ohm]
  motor.KV_rating = 30;
  motor.voltage_limit = 0.56*driver.voltage_power_supply;
  motor.current_limit = 10; 
  //motor.phase_inductance = 0.002;
 
  motor.torque_controller = TorqueControlType::foc_current;
  motor.controller = MotionControlType::velocity;
  
  motor.PID_velocity.P = 2;
  motor.PID_velocity.I = 0;
  motor.PID_velocity.D = 0.005;
  motor.PID_velocity.output_ramp = 200;  
  motor.LPF_velocity.Tf = 0.03;
  
  //motor.monitor_variables =  _MON_CURR_Q | _MON_CURR_D | _MON_VEL;
  motor.monitor_variables =   _MON_CURR_Q | _MON_CURR_D;
  motor.monitor_downsample = 50;

  motor.PID_current_d.P= 0.1;
  motor.PID_current_d.I = 20;
  motor.PID_current_d.D = 0.0003;

  motor.PID_current_q.P = 0.01;
  motor.PID_current_q.I= 8;
  motor.PID_current_q.D = 0.0001;

  motor.LPF_current_d.Tf = 0.003;  
  motor.LPF_current_q.Tf = 0.003; 
  
  motor.PID_current_d.limit = motor.voltage_limit;
  motor.PID_current_q.limit = motor.voltage_limit; 
  
  //motor.PID_current_q.ramp = 100000; 
  //motor.PID_current_d.ramp = 100000;
  
  //motor.foc_modulation = FOCModulationType::SinePWM;
  motor.foc_modulation = FOCModulationType::SpaceVectorPWM;

  motor.init();
  motor.initFOC();
  Serial.println("Motor ready.");  

  command.add('T', onTarget, "target velocity");
  //command.add('M', onMotor,  "motor");
  command.add('D', onPid,"d_current_PID");
  command.add('Q', onPid1,"q_current_PID");
  command.add('E', enableSmoothing, "enable smoothing");
  command.decimal_places = 4;
  //command.motor(&motor,"E0");
  _delay(100);
}


void loop() {

  motor.loopFOC(); 
  //analog_read_A0 = analogRead(PA0);
  //analog_read_A0_filtered = filter(analog_read_A0);
  //target_velocity = float(map(analog_read_A0_filtered, 0, 4096, 0, 6010))/100;
  target_velocity = motor.target;

  
  if (target_velocity <= 0.5f && motor.enabled==1){
     motor.disable();
     
  }
	if (target_velocity > 0.5f && motor.enabled==0){
      motor.enable();
  }
    
  motor.move(target_velocity);
  
  motor.monitor(); 
  command.run();
   
}
1 Like

Hi,

This topic was already discussed here but for low side current sensing which is more tricky.

It resulted in this fix that is not available on the master branch yet. Basically it resets the PIDs when disabling the motor.

You could try this after disabling:

motor.PID_velocity.reset();
motor.P_angle.reset();
motor.PID_current_q.reset();
motor.PID_current_d.reset();

Switching to Torque mode with 0 target wouldn’t be ok for freewheeling ?

1 Like

You could keep the velocity control type and make the velocity pid limit 0 also:

motor.PID_velocity.limit = 0;

Then setting it back to the original limit.

Hi @Candas1 thank’s a lot for you answer.
I tried the approach with switching to torque mode and setting the target current to 0 to achieve freewheeling. Then i switch back to velocity controll when i want to power the motor. This helped a lot and the current spiking is gone. Also made the deceleration smoother, wchich is desireable for my application. Thank’s again i did not think about it.

But could you please help me understand what is the difference between the motor.disable() approach and the target_current = 0 approach, when it comes to what is happening with the surpluss energy from the motor, when in deceleration?

To my understanding the motor.disable() closes all of the gates, puts them in High Z (i use the 6PWM driver) . This leaves the phases floating and when the floating voltage exceedes the source voltage, the energy flows through MOSFET’s body diodes back to the source and increases the voltage at the MOSFET drain. As i don’t have any dedicated solution for handling it, some of the energy goes into the bulk capacitors, and some decipates on the body diodes and motor windings as heat (correct me if im wrong please).

But i wonder what happpens with the surpluss energy when the target_current = 0, and the motor is deceleraiting. Trying to find information about it, but didnt yet manage to do so.

I am no electronics expert but I think this is right, someone else might comment.
I don’t think we are talking about much energy here, as we are not talking about regenerative braking yet. There is another thread about this topic.

Your problem was that when disabling, the PIs are not running anymore, and delta time is very high when enabling. It will be solved in the next release.

When switching to torque mode the PI controllers still regulate the voltage to achieve 0A Q and D axis current, so you don’t face the problem and you can still see the DQ currents.

I am curious if the last solution also works, it would mean the Velocity PID is also still running, but it’s output is always limited to 0 torque.

This is essentially correct, as far as I am able to say :slight_smile: also not an expert. The energy generated in the windings by the still turning motor flows through the body diodes - if you’re using a battery, this will essentially charge the battery. For other power supplies it can be a problem if they can’t handle being back-powered.
For larger motors a braking/shunt circuit is probably essential.

If the target current is 0, the motor is decelerating only due to the bearing friction. The supplied voltage will be counteracting the BEMF voltage, to produce 0 net current. So in this situation no current flows and there is no surplus energy, I would have said.

Of course due to inefficiencies and non-perfect components there will still be some loss in real life, which will manifest as heat on the components, I guess.

Hi @runger thank you for you answer

Would that mean that most of the energy from the motor is being discharged into the ground? I noticed that when i turn the motor strongly by hand, when the target_current = 0, it does show a small increase of voltage on the power supply (i dont have a battery conected yet). So either i missunderstood something or, it’s the regulator that reacts with a delay, and some of the energy still goes back into the source before the regulator counterreacts the BEMF.

@Candas1 the aproach with motor.PID_velocity.limit = 0, is kinda working kinda not. Sometimes the motor decelerates to 0, sometimes it decelerates and keeps slowly turning. Also it causes an instability when going back to the previous limit value and aceleraiting again, and the regulator overshoots the current, similarly to the situation with the motor.disable().

EDIT: but i think that it is a different problem than with the motor.disable() couse the current sensing continues to work, the effects are just similar.
I also tried to reset the PID when calling motor.disable() but i had a similar outcome as you described in the thread you linked before. The overshoot was gone, when before the restart the motor was completely stoped. If there was still a rotation, the current overshoot was still there.

OK then I am wrong, thanks for trying.

I really think there is not much energy in this situation - the motor is spinning due to its own inertia, and generating a BEMF voltage. Normally this would generate a current opposing the motion, and act to slow the motor, and produce energy while doing so (motor as generator).
But if you set a 0 target current, the controller will generate a “forward voltage” to oppose the BEMF voltage - to produce the desired 0 target current. So then the BEMF voltage and the driven voltage “cancel out” so to speak, no current flows, and no energy is consumed or produced (ideally speaking, of course).

That’s kind of the whole idea behind the 0-current coasting. If everything is set up right and tuned well, then the motor should coast to a stop as the bearing friction, air resistance and other kinds of friction overcome its angular momentum, because the braking action of the BEMF is cancelled out. How quickly this happens will depend on the amount of friction (e.g. better bearings coast for longer) and the amount of angular momentum (e.g. if the motor has a 1kg weight attached that you have brought up to speed, then it will take much longer for the friction to bring this to a stop than if the motor were unloaded).

Good question - you mention turning it strongly, so possibly a number of different things is happening: the current measurements have a low pass filter, so it may take some time to assimilate the sudden change. The PIDs for current have limits, so they may not be able to correct for your change so quickly, or in fact the change may exceed the ability of the driver to compensate it. If your “strong turn” causes a reaction in excess of the voltage limit set to the motor, then it won’t be possible to cancel it. Probably there are a number of other explanations, or it could be a combination of reasons…

1 Like

Thank you very much for the explenation @runger , I understand now. I seems like it’s overall a better approach for coasting, when you dont have any regen or brake resistor. You will expend some energy for opening the MOSFET’s for counterreacting the BEMF, but less of the energy will go back into the source so it seems like a safer option.