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 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. 
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 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)
– SarveshPatil7