# Auto-tuning current control loops

Since the moteus controller was first released, it has implemented a two-stage controller. The outer loop is a combined position/velocity/torque PID controller, which takes as an input a position trajectory, and outputs a desired torque. The inner loops accepts this torque, and uses a PI controller to generate the Q phase voltage necessary to achieve that torque.

Until now, the constants for that PI controller were left as an exercise for the reader. i.e. there were some semi-sensible defaults, but the end-user ultimately had to manually select those constants to achieve a given torque bandwidth. That isn’t too much of a problem for a sophisticated user, but for the rest of us, it is hard to know how to go from a desired torque bandwidth to reasonable PI gains.

Now, I’ve finally decided to tackle this problem! There are two phases, the first being how to measure the inductance of a given motor, which is last major motor parameter to be automatically measured. The second is how to generate a controller using the resistance and inductance which has a desired bandwidth.

## Measuring inductance

An inductor is an energy storage device where the rate of change of current is directly proportional to the voltage across its terminals. Most (probably all?) electrical motors act as inductors in various forms. The hobby outrunners typically driven by moteus controllers, surface permanent synchronous magnet motors, can be treated as two equal valued inductors across the imaginary D and Q axes.

Given an ideal inductor, an easy way to measure that inductance is to apply a constant voltage and see how fast the current rises. That’s roughly the approach I took with moteus, but instead of a fixed voltage, it applies a square wave voltage centered about 0 across the D axis. That way the motor doesn’t move, the net current is 0 over time, and the average rate of change can be integrated over many cycles for much higher accuracy.

This “square wave” mode is implemented as a new control mode in moteus, primarily intended to be invoked by moteus_tool during the calibration process. You can specify the half-period of the square wave in control cycles, and the voltage to apply across the D axis. Here’s a plot of the current, as reported by a moteus controller over its debug DAC connection during a test on an mj5208:

This shows that yes, the current is roughly triangular for the input square wave.

## Designing a current controller

Given this newly measured inductance, and the phase resistance that the calibration process already measured, we have sufficient information to design a PI controller which achieves a desired bandwidth response.

For a PI controller, a simple second order approximation of the closed loop transfer function can be written as:

$I(s)=\frac{K_p s + K_i}{L s^2 + (R+K_p) s + K_i}$

Where L is the D/Q phase inductance, R is the phase resistance, and Kp and Ki are the PI gains. Taking that, if we constrain:

$K_i = K_p \frac{R}{L}$

We can then substitute this into the original transform, which results in a first order system:

$I(s)=\frac{K_p}{L} \frac{1}{s + \frac{K_p}{L}}$

This system has a 3db frequency at the sole pole:

$w_{3db} = \frac{K_p}{L}$

Which then allows us to fully determine all the remaining parameters:

$K_p = w_{3db} L$
$K_i = w_{3db} R$

Finally, when we double check our results, the “rise time” for a first order system (roughly the time it takes to go from 10% to 90%) can be described as:

$t_{rise} = \frac{0.35}{BW_{hz}}$

A motor like the mj5208 can be approximated as a phase resistance of 0.04 ohm, with a D/Q phase inductance of 25e-6 H. For a 3dB bandwidth of 1000 radians per second (~160Hz), the gains measured in radians per second would be:

$K_p=0.025$
$K_i = 40.0$

To set these on the controller, we would use the following parameters:

servo.pid_dq.kp=0.025
servo.pid_dq.ki=40.0

## Empirical testing

To make sure these make sense, I used this process for a few different bandwidths on a few different motors. For each, I used an oscilloscope to measure the rise time of the current waveform in response to a step input of a relatively small 4A command. The shape of that curve lets us be pretty confident that the damping ratio is correct, and the total rise time of the curve gives a good measure of the overall bandwidth that was achieved.

Across the motors I tested, the resistance varied from 35-65 milliohm, with inductances varying from 9uH to 33uH. The grid shows that the bandwidth worked out pretty close to the target in all those cases.

## moteus_tool integration

Now that the math is out of the way, you don’t need to worry about it at all, because as of version 0.3.17 of moteus_tool (with version 2021-04-09 of the firmware), this whole process is automated. When you run the calibration step, you can just specify the desired 3db bandwidth on the command line of moteus_tool and it will calculate and set the parameters for you. By default it will select 100Hz bandwidth.

python3 -m moteus.moteus_tool -t 1 --calibrate --cal-bw-hz 100

## Video

If you want to see this content explained more verbosely, here’s a video that covers most of it!

## 8 thoughts on “Auto-tuning current control loops”

1. Ricky says:

Nice Work!

How did you deal with the inductance values(Ld, Lq) as the electrical angle changes?
Also the frequency and the voltage you injected to d axis? (according to the your code, it is 5kHz and 0.45V?)

La,Lb,Lc – the phase inductances – are changing as the elctrical angle, and Ld, Lq aren’t?

Thanks!

Like

1. When measuring the inductance, only the D axis inductance is measured. It is assumed that the D and Q inductance are about the same, which is largely true for the SPMSM motors that people use with moteus.

Like

1. Ricky says:

You are absolutely right.

For PMSM motors, we can say that Ld and Lq are the same and I agree with the way that using for measuring d axis inductance.

But when I measured the value of d axis inductance, it seems that the value can be changed as where the electrical angle is.

In the moteus code, the inductance is measured at random electrical radian, which may mean the value could be randomly inaccurate.

That is the major pain point.
I measured the d axis inductance at 0 rad(electrical) and at random rad(electrical), they were different.

It was measured from 50uH to 120uH on the motor I am using..

I am trying to check the d axis inductance vs electrical angles and hope I can share the results.

Am I doing something wrong?

Thank you!

Like

2. For PMSM motors, Ld and Lq are not generally the same, thus the motors have a measurable saliency and the inductance varies by rotor position.

For the more specific case of SPMSM motors, Ld and Lq are nearly equal and the same for all rotor positions, which is the behavior of the vast majority of hobby motors.

As you noted, moteus’s current inductance auto-detection will not work that well if the motor has significant salience. You can just ignore its auto-detection and configure the current PI control manually in that case.

Like

2. Ricky says:

mathworks.com/help/physmod/sps/powersys/ref/permanentmagnetsynchronousmachine.html

Like

3. Ricky says:

Hi Josh,

I agree with the fact that SPMSM has the same inductance for all rotor position but IPMSM.

Here is the image I tested today.
Although the trend of the pattern looks similar, but the inductance value differs as the applying voltage and frequency are changed,
and the measured data varies by the electrical angle.

The motor I am using is T-Motor AK80-6, and I am quite confident it is SPMSM..
https://store-en.tmotor.com/goods.php?id=981

Then since the inductance are changing by the rotor position, the motor cannot be said it is SPMSM And I need to measure PI Controller gain manually?

Or, If I can measure all inductances from 0 to 2pi of electrical angle, I can choose one represented value among them?

Thanks. You are the best.

Like

1. Ultimately, you’ll want to tune the bandwidth anyway, so it doesn’t matter that much in practice what method you use.

Have you considered joining the mjbots discord? You can get much faster response and talk with more people! https://discord.gg/W4hUpBb

Like

1. Ricky says:

Hi, Josh!

Good Idea. I am new to discord but I will try!

Thank you so much!

Like