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.
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.
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?
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
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. 
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!