Compensating for cogging

Hey all! I have a setup with a GM3506 motor, a MKS Dual FOC Plus V3.2, a AS5048A encoder, and a WeAct STM32G474 board.

When running in open loop velocity mode, one revolution per second, averaging and plotting the measured velocity for one revolution I get this waveform:

It’s a 24N/22P motor. The waveform repeats perfectly after half and looks pretty consistent over time.

Is there a nice open loop way to compensate for this, to get smooth rotation without the PID loops having to try and keep up? I’d like to remove as much of the vibrations as possible.

10 Answers

10

In theory it should be a matter of characterizing the cogging and compensating for it, right? It feels like I should be able to iteratively run a loop, and have it play with a compensation table for a single (or half of a) rotation, until it’s running smooth? At least for a single drive current that should be feasible. Or are there too many variables (temperature?) involved?

I was considering doing this by breaking down my basic compensation LUT into component frequencies with a DFT and then individually tweak the significant 20 or so harmonics with gradient descent optimization to minimize the ripple... Then realized how inefficient that would be (my current setup is battery driven so the voltage keeps dropping over time)

I wrote a simple bit of code to give this a try. The approach has two phases:

Lookup table generation phase:

  • Run the motor in closed loop velocity mode for a couple of revolutions
  • Average the current required to get to the target velocity at each angle
  • Save this to a table

Compensated phase:

  • Run the motor in closed loop velocity mode
  • Subtract the current saved to the lookup table from the PID error

This way the PID shouldn’t have to compensate for the cogging anymore.

The results:

You can see it switch from the first to the second phase at 1/3 of the screen.

The dark purple line is the velocity - it does actually run smoother with!

Here’s the (simplified) code for the sample gathering:

// Number of samples per full revolution
#define SAMPLE_COUNT 2000
	
float Iq_readings[SAMPLE_COUNT] = {0};
uint16_t sample_counts[SAMPLE_COUNT] = {0};
	
// Seconds per revolution
#define SPR 20
	
// Loops to get samples for
#define REVOLUTIONS 3
	
// Period of a single control loop in us
#define CONTROL_LOOP_PERIOD 200
	
int tableIndex;
	
void fillTable(){
	
	motor.controller = MotionControlType::velocity;
	motor.target = TWO_PI / SPR;
	
	// Gather samples for the amount of loops specified
	for (int i = 0; i < ((1000000 / CONTROL_LOOP_PERIOD) * SPR * REVOLUTIONS); i++){
	
		unsigned long loopTimestamp = micros();
	
		unsigned long current_time = micros() / CONTROL_LOOP_PERIOD;
	
		// Map position to LUT index
		tableIndex = ((int)round(encoder.getMechanicalAngle() / TWO_PI * SAMPLE_COUNT)) % SAMPLE_COUNT;
	
		// Accumulate q-axis current values for averaging
		Iq_readings[tableIndex] += motor.current.q;
		sample_counts[tableIndex]++;
	
		printFloats(motor.current.q, Iq_readings[tableIndex], motor.current_sp, motor.shaftVelocity());

		motor.loopFOC(0);
		motor.move();
	
		loopTime = micros() - loopTimestamp;
	
		while (micros() / CONTROL_LOOP_PERIOD == current_time);
	}
	
	// Compute average
	for (int i = 0; i < SAMPLE_COUNT; i++){
		Iq_readings[i] = Iq_readings[i] / sample_counts[i];
	}
}

Then to run with compensation I have this:

while (1){
	unsigned long loopTimestamp = micros();

	unsigned long current_time = micros() / loopPeriod;

	// Compute floating point index
	float tableIndex_float = encoder.getMechanicalAngle() / TWO_PI * SAMPLE_COUNT;
	int index1 = (int)tableIndex_float;  // Lower index
	int index2 = (index1 + 1) % SAMPLE_COUNT;  // Wrap around safely

	// Compute interpolation factor (fractional part)
	float alpha = tableIndex_float - index1;

	// Interpolate between two closest values
	float Iq_interp = Iq_readings[index1] * (1.0f - alpha) + Iq_readings[index2] * alpha;

	// Apply interpolated value to FOC
	motor.loopFOC(-Iq_interp);
	motor.move();

	printFloats(motor.current.q, Iq_interp, motor.current_sp, motor.shaftVelocity());

	loopTime = micros() - loopTimestamp;

	while (micros() / loopPeriod == current_time);
}

And I modified loopFOC like this:

void BLDCMotor::loopFOC(float cogging_compensation) {
... 
      // Apply cogging torque compensation to q-axis current setpoint
      compensated_current_sp = current_sp - cogging_compensation;

      // Calculate the phase voltages
      voltage.q = PID_current_q(compensated_current_sp - current.q);
      voltage.d = PID_current_d(-current.d);
...

There’s a lot more to play with, but it’s a start. :slight_smile:

That's awesome! I'll have to try it. That should also save a lot of power as well, for battery applications

Loved the implementation by @LotsOfNaan , however, I’m using the SimpleFOC mini which doesnt support current sense. So I took some inspiration from his idea and used the derivative of velocity response with very low voltage in torque mode to calculate a compensation LUT. It probably wont be as accurate as using the current sense, but heres what I have at the moment. I’m still working on it and will update once the results are satisfactory.

That said - not at all consistent with drive strength. This is the same code, but an increased supply voltage:

I have often wondered if it’s possible to run the PID loops fast and accurate enough so they compensate for the cogging. But haven’t managed to achieve this in practice. I don’t think it’s possible to eliminate cogging in open loop mode, unless you purchase a “slotless” (ironless windings) motor.

That would be a fantastic thing to have if it’s feasible!

Looks like some people have done it here: Anti-Cogging Algorithm Brings Out The Best In Your Hobby Brushless Motors | Hackaday

This is fantastic. Maybe we can implement it as a module in the SimpleFOC library?

I wonder if it would be better to measure the torque ripple in open loop mode, would it provide a cleaner reading without interference from the velocity PID controller?

I think some more experimenting would be required before implementing it in the library. Does it hold up with different speeds? Does it hold up with different loads? Does it hold up in both directions?

I’m not sure if open loop would be better for computing the required Q current to keep it running smooth at each angle, as there would indeed be less interference from the velocity controller, but the variations in speed would be larger.

Running slow enough I don’t think the PID loop would pose a problem, especially when properly tuned.

I’m having some troubles on this hardware as the current sense is ridiculously noisy, so I have to heavily filter it and can’t tune the current loops tight enough. Still very ripply in the screenshots I sent. Hope to get that fixed with custom hardware soon!

That looks pretty sweet / smooth for not even having current sense!

I’ve been working on this in the meantime as well, and something I didn’t realise earlier is how big a role friction plays in this: The only way to get this to accurately work for different speeds is by isolating cogging torque and friction torque by running the LUT generation in both directions and averaging the results.

If you keep the directional friction torque results in another LUT you can even compensate for those. Feels really weird (in a fun way) on gimbals as they’ll feel like you’re turning the smoothest bearing ever

Hi guys,

Do you think that the calibrated sensor class from the drivers repo would work in this case?

It’s more intended to account for sensor eccentricity than the cogging though.

Is there some fundamental differences between the two?

It also does not use the current but velocity and voltage control. And instead of calculating the feed forward correcting torque term it corrects the “equivalent” sensor angle.

I’m unsure what your question actually is!

Ah there is no real question. :upside_down_face: I'm just wondering if this feature is something that don't already have in the library (drivers) and should we implement it. So the question for me is: Is there a difference between the eccentricity compensation done in the calibrated sensor class and your code? And would the calibrated sensor do the job or it is fundamentally different.

I don't think CalibratedSensor would work for cogging since it assumes that open loop positions are evenly spaced, and cogging causes them to not be evenly spaced. But measuring velocity to generate the lookup table may work for both cogging and eccentricity. I think the fundamental difference between the two is whether the correction is applied as an offset to the electrical angle or to the commanded current. If both need compensating, it would be difficult to sort out how much of each you should do.

In the case of stepper motors, you could first run eccentricity calibration by assuming the full step positions are evenly spaced, and then the residual velocity variation should be entirely due to cogging torque. When I was working with a tiny 9N12P BLDC with strong cogging, it only had two reasonably reliable positions per pole pair. I don't think you could get a good eccentricity calibration with it.