Motor Pole Pairs and Rotor Position

Delancy7

1 mW
Joined
Apr 3, 2024
Messages
14
Location
California
I'm working on FOC motor control for an electric go-cart. I've inherited some code in C for this from my dad, who had worked on this several years ago. I want to complete the code and get it working on a motor I bought for use in a go-cart. The code is written for an STM32F7 MCU.

There's something in the FOC code that I don't understand. When dad reads the rotor position in the motor control inner loop (triggered from the timer that does PWM on the motor phases), he calculates the rotor position by reading the position from the quadrature encoder (the motor he used has a QE with 2000 counts per revolution). This motor (according to the specs) has 8 poles, hence 4 pole-pairs.

What I don't understand is why dad's code to calculate the rotor position (in radians from 0 to 2*pi) doesn't use the number of motor poles in the calculation. Is this correct? I would have thought that the number of pole-pairs would figure into the calculation because this motor makes 4 electrical revolutions for every mechanical revolution. What am I missing here?
 
I'm working on FOC motor control for an electric go-cart. I've inherited some code in C for this from my dad, who had worked on this several years ago. I want to complete the code and get it working on a motor I bought for use in a go-cart. The code is written for an STM32F7 MCU.

There's something in the FOC code that I don't understand. When dad reads the rotor position in the motor control inner loop (triggered from the timer that does PWM on the motor phases), he calculates the rotor position by reading the position from the quadrature encoder (the motor he used has a QE with 2000 counts per revolution). This motor (according to the specs) has 8 poles, hence 4 pole-pairs.

What I don't understand is why dad's code to calculate the rotor position (in radians from 0 to 2*pi) doesn't use the number of motor poles in the calculation. Is this correct? I would have thought that the number of pole-pairs would figure into the calculation because this motor makes 4 electrical revolutions for every mechanical revolution. What am I missing here?
It's in there somewhere, that factor of 4. Might be hidden, rolled up in don't other constant or whatever.... But it's in there. It won't work without it.
 
It's in there somewhere, that factor of 4. Might be hidden, rolled up in don't other constant or whatever.... But it's in there. It won't work without it.
I don't see where the factor related to pole pairs appears in the calculation. Here's dad's code:
Code:
#define POLE_PAIRS        (8)                   // Hudson motor
#define ENC_COUNTS        (1000)                  // Hudson motor
position = QEGetPosition();
    //theta = ((position >> 2) % (ENC_COUNTS/POLE_PAIRS)) * 360.0f / (float)(ENC_COUNTS/POLE_PAIRS);
    theta = ((position % 1000) * 360.0f) / 1000.0f;

Theta is a float; position is a uint32_t.

The code as shown above works. If I uncomment the second line and comment the third line, the code doesn't work. I'm puzzled as to why. I'm also puzzled as to why 1000 appears in the uncommented line that works--the encoder is 2000 counts per mechanical revolution, not 1000. It looks like the third line was a quick-and-dirty change to make things work, but I don't understand why.

Anyone have any idea what might be going on here?
 
Last edited:
I don't see where the factor related to pole pairs appears in the calculation. Here's dad's code:
Code:
#define POLE_PAIRS        (8)                   // Hudson motor
#define ENC_COUNTS        (1000)                  // Hudson motor
position = QEGetPosition();
    //theta = ((position >> 2) % (ENC_COUNTS/POLE_PAIRS)) * 360.0f / (float)(ENC_COUNTS/POLE_PAIRS);
    theta = ((position % 1000) * 360.0f) / 1000.0f;

Theta is a float; position is a uint32_t.

The code as shown above works. If I uncomment the second line and comment the third line, the code doesn't work. I'm puzzled as to why. I'm also puzzled as to why 1000 appears in the uncommented line that works--the encoder is 2000 counts per mechanical revolution, not 1000. It looks like the third line was a quick-and-dirty change to make things work, but I don't understand why.

Anyone have any idea what might be going on here?
2000 counts might well be 8000 pulses in quadrature.

So 8pp and there's your 1000

But it's hard to tell from tiny code snippets. Be sure though, if it spins, the numbers work out somehow you just need to think hard and look more carefully.
 
Thanks @mxlemming

I checked the motor part number and it's actually 4000 counts per mechanical rotation. The STM32 timer is set up to count edges, four edges per encoder count. That would mean the QE timer sees 4000*4 or 16,000 edges per mechanical rotation.

The timer is initialized (in TIM4->ARR) with 1000*4 - 1. So it counts 0-4000, or 1/4 of 16,000, which will equal one electric rotation (the motor has 8 poles, hence 4 pole pairs). That's why the hardcoded values in my second post happen to work.

Here's another basic question: Does FOC work with position as the mechanical rotor position or the electrical rotor position? I'm assuming it is the electrical position, but want to confirm.

Another thing has me puzzled. Dad's code uses a SVPWM algorithm to calculate the PWM counts for the three motor phases. His code passes the rotor position from the quad encoder (as described above) to this SVPWM function, but instead of using it, he calculates it from scratch and uses that value. Here's the code that does the calculation:
Code:
    theta = atan2f(beta, alpha);

I'm really puzzled why he ignores the rotor position passed as a parameter completely and calculates it from scratch.

Right before calling the SVPWM function, he does an inverse Park transform and passes it the rotor position derived from the quad encoder. That returns the alpha and beta values that are passed to the SVPWM code and are subsequently used to calculate theta using arctan. Why do this rather than using the QE rotor position directly?
 
Thanks @mxlemming

I checked the motor part number and it's actually 4000 counts per mechanical rotation. The STM32 timer is set up to count edges, four edges per encoder count. That would mean the QE timer sees 4000*4 or 16,000 edges per mechanical rotation.

The timer is initialized (in TIM4->ARR) with 1000*4 - 1. So it counts 0-4000, or 1/4 of 16,000, which will equal one electric rotation (the motor has 8 poles, hence 4 pole pairs). That's why the hardcoded values in my second post happen to work.

Here's another basic question: Does FOC work with position as the mechanical rotor position or the electrical rotor position? I'm assuming it is the electrical position, but want to confirm.

Another thing has me puzzled. Dad's code uses a SVPWM algorithm to calculate the PWM counts for the three motor phases. His code passes the rotor position from the quad encoder (as described above) to this SVPWM function, but instead of using it, he calculates it from scratch and uses that value. Here's the code that does the calculation:
Code:
    theta = atan2f(beta, alpha);

I'm really puzzled why he ignores the rotor position passed as a parameter completely and calculates it from scratch.

Right before calling the SVPWM function, he does an inverse Park transform and passes it the rotor position derived from the quad encoder. That returns the alpha and beta values that are passed to the SVPWM code and are subsequently used to calculate theta using arctan. Why do this rather than using the QE rotor position directly?
Svpwm doesn't require angle as an argument so if that's happening after the inverse Park, it's not clear why he's doing it atall.

It's possible to derive the angle from a variety of sensorless observer means and many of them result in an arctan as the final step, so without the rest of your code... I'll guess he's either got a sensorless option or he's doing some kind of power factor monitoring/correction, since atan2f(alpha, beta) will return the electrical phase angle of the output assuming alpha beta are the output voltage. This will not be in phase with the current or the rotor angle for the general case.
 
He's not doing any kind of sensorless stuff in his code. It's all based on a quadrature encoder. I doubt that there's any kind of power factor correction in there either.

Can you elaborate on this statement?
This will not be in phase with the current or the rotor angle for the general case.
 
Another peculiar thing I noticed is when the motor is running at 2700 RPM, the current supplied by the power supply to the motor is 1.0A at 24v. When the motor is running at 2900 RPM, the current at the PS is 0.6A at 24v. Both with no load.

Why would the motor consume 40% less current when running at a faster RPM?
 
Perhaps higher BEMF cancelling out more of the input current?
That makes sense, thanks.

My next question: when monitoring the phase currents read by this code, the values seem to vary wildly, almost if they're random noise. I can use a very stiff IRR filter to tame this noisy current, but don't think it should be that noisy. The controller has low-side shunts in two of the phases and I'm using Iu + Iv + Iw = 0 to calculate the current of the phase without a shunt. Even when the motor is spinning at a very steady RPM the currents are all over the place.

This has me wondering if this code is reading the ADC at the proper place in the PWM cycle. The code uses center-aligned PWM Mode 2 with the non-inverted output high when CNT > CCR1. When should the ADC be trigged to read the shunt voltages?
 
That makes sense, thanks.

My next question: when monitoring the phase currents read by this code, the values seem to vary wildly, almost if they're random noise. I can use a very stiff IRR filter to tame this noisy current, but don't think it should be that noisy. The controller has low-side shunts in two of the phases and I'm using Iu + Iv + Iw = 0 to calculate the current of the phase without a shunt. Even when the motor is spinning at a very steady RPM the currents are all over the place.

This has me wondering if this code is reading the ADC at the proper place in the PWM cycle. The code uses center-aligned PWM Mode 2 with the non-inverted output high when CNT > CCR1. When should the ADC be trigged to read the shunt voltages?
Unfortunately amberwolf comment doesn't really make sense. You either have measurement error, increased ripple at lower duty or a control scheme error. It shouldn't be the case that slower uses more current.

You should be triggering the ADC at either cnt=0 or cnt=arr with center aligned PWM depending exactly how you wired it. From your description, 0 sounds correct, when the low side MOS are on.

Iirc the st timer didn't trigger properly when you choose 0 or ARR so I have it set at ARR-1 in my code.

2 low side shunts is a terrible solution. It's simply not worth the cost saving. It becomes very hard to accurately sample the currents at high duty cycle, this isn't immediately apparent at low power but when you start cranking up the amps it all gets a bit rough.
 
Unfortunately amberwolf comment doesn't really make sense.
Sorry, it was just a guess; I don't know how any of the code works, only that the faster a motor spins the more BEMF it creates, for a given design.
 
2 low side shunts is a terrible solution. It's simply not worth the cost saving. It becomes very hard to accurately sample the currents at high duty cycle, this isn't immediately apparent at low power but when you start cranking up the amps it all gets a bit rough.

Yep, agreed. This controller is a small TI DRV8301 board and shunts were probably the cheapest solution.

I eventually will build my own boards, and will probably use Hall current sensors in each phase rather than shunts, but that's still sometime in the future and I need to work with what I currently have.
 
Back
Top