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 50Hz 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!

Unsuccessful CAN-FD communication between CANBed-FD and moteus

On the mjbots discord, people are often looking for the cheapest possible way to command and monitor a moteus controller. One possible solution that comes up over and over again is the CANBed-FD board, as sold by Seeed Studio (and others). I decided to get one of these in house to see if I could make it work:

The first thing I figured out was that the DB-9 connector used a non-standard pinout for CAN_L and CAN_H. I just switched to the terminal block connections instead of the DB-9 to get around that. For the software, I decided to use the acan2517FD Arduino library, as it was quite a bit more robust and featureful than the one provided by Longan Labs.

With this in hand, I was able to get the Arduino to send properly formatted CAN-FD frames that moteus would receive. However, as it often does, the problem came when receiving. This Longan Labs board uses a 20MHz clock for the MCP2518FD onboard, which means that it is only *just* able to do the 5MHz CAN-FD clock rate that moteus requires, and there is no margin for actually being able to synchronize to a slightly skewed bitrate using the sync jump width. Because of this, I was not able to get the device to receive data from a moteus controller except by nudging the bitrate of the moteus controller around by fractional percents manually, and then it only worked for a short time before temperature, or humidity, or whatever, drifteed it out of range.

If someone finds an Arduino board that uses a MCP2518FD with a 40MHz clock (or a 4MHz clock that uses the built in PLL), let me know, as I expect that should be able to work fine. That is the configuration used by the Seeed Raspberry Pi CAN-FD adapter, which has 2 MCP2518FDs with a 40MHz clock and it has been demonstrated to work with bit timings listed in the moteus reference documentation.

mjbots power_dist r4.3b

I’d like to introduce the newest mjbots product, an updated revision of the power_dist, 4.3b available at mjbots.com today!

This version has a number of improvements over the previously released r3.1:

r4.3br3.1
Voltage Range10-44V8-34V
Maximum load capacitance4,000 uF400 uF
Quiescent Current300uA5mA
Current (Continuous / Peak)45A / 80Aunrated / 100A
Energy MonitoringYESNO
Switch ModeHigh SideLow Side
Dimensions50x80mm45x70mm
Price$139$79

The only real downsides are that is more expensive and slightly larger.

If you want to read up more on the motivation and design process for this board, you can see that in (part 1, part 2, part 3, part 4).

Failed power_dist r4 designs (part 4)

For context, see part 1, part 2, or part 3.

r4.0

My first attempt at an r4 design was based on the TI LM5066 hot swap controller. It is one of the more full featured controllers, since it supports built in energy monitoring over a SPI bus with no additional components. This first iteration was actually surprisingly close to being workable. There were two factors that it performed poorly on, quiescent current and energy monitoring. The quiescent current was similar to the r3.1 version. Energy monitoring was present, but at the full scale range necessary for power_dist, it was almost unusably inaccurate. With a design set for a peak of 100A, and also the lower 25mV current sense range, the current noise was measured in multiple amps.

I definitely wanted usable energy monitoring, and wasn’t quite ready to sacrifice the quiescent current, so decided to try again.

r4.1

This version was based on the Analog LTC4380, nominally a “surge stopper” rather than a hot swap controller. I was attracted to it because it seemed like it would offer a similar pre-charge function, but with a much reduced quiescent current. I implemented energy monitoring using a separate Analog LT1787 high side current sense monitor which was fed into the op-amps on the STM32G474.

This version had multiple new and different problems. For one, the “fault timer” on the LT4380 is active during the “pre-charge” window. It turned out to be impossible to select a pre-charge speed that would handle a reasonable capacitance while simultaneously not triggering the fault timer.

Secondly, I discovered that the LT1787 has a relatively high output impedance, such that significant error was introduced by feeding it directly into an inverting op-amp with gain on the STM32G4. The G4 is relatively configurable, but there was no way with the existing PCB to first run it through a buffer op-amp to improve the impedance before moving to a gain stage (and only then on to the onboard ADC).

Finally, this version had the first incarnation of a “latching” circuit that would allow the processor to shut off power to the entire 3.3V bus when desired, but flipping the primary power switch would turn it back on. Thus the processor could decide how long to run after the primary power switch was turned off. Unfortunately, this circuit had several problems in this iteration that needed resolving.

r4.2

This version used the TI TPS2490 hot swap controller along with the same LTC1787 high side current sense amplifier. It re-arranged the current sense input so that the STM32G4 could first buffer, then amplify the signal before handing it off to the ADC. The biggest problem here was once again the circuit that attempted to let the STM32G4 shut itself off to lower quiescent current. It ended up being unworkable, and also rendered the TPS2490’s under-voltage protection non-functional.

r4.3

This version is basically the one that went into production. It still uses the TPS2490 and LTC1787, but now has a simpler latching mechanism, and undervoltage protection that actually works.

r4.3b

The only changes here are silk-screen modifications!

Next up

In the next, and final post, I’ll introduce r4.3b and its specifications!

Hot swap controllers (next get power_dist part 3)

This is one of a series covering the new mjbots power_dist board. See part 1 and part 2 for more context.

As mentioned previously, hot swap controllers are primarily used to allow a card to be inserted live into a server backplane, while minimizing disruption to the primary power bus while doing so. Additionally, they often implement protection features like over-current and short-circuit protection, and some support energy monitoring.

Typical topology

A typical hot-swap topology looks like:

Here, the high side FET is used for two purposes. During the “pre-charge” phase, the FET is operated in the linear regime, with a large voltage across it until the load capacitance is fully charged, at which point the FET reaches its “fully on” state and operates with its stated Rds on resistance. Notably, this means that the entire pre-charging energy is dissipated in those FETs, as opposed to the r3.1 design where a power resistor serves that purpose.

To accomplish the protection functionality, the current shunt (R1) is used to measure total current moving through the primary FET. Depending upon the particular chip, this could include over-current monitoring, where the FET is brought back into the linear operating region to limit the current, or short-circuit protection, where the FET is immediately turned off. Additionally, the resistor divider (R2/R3) can be used to program an undervoltage threshold.

Design constraints

The biggest challenge that a design faces with any hot swap controller is selection of the primary FET. Because it has to dissipate the entire pre-charge energy in a short time window, the device becomes very stressed. This limits the total capacitance that can be charged, the maximum voltage, and somewhat non-intuitively, the current that can be drawn during this startup window. For a design like the power_dist, where there is no “power good” signal distributed to downstream loads, they can draw what are effectively constant-power loads soon after the bus voltage reaches a valid intermediate state. Given that this can occur when there is still a large voltage across the high side FET, it can add a lot of energy dissipation.

To make this even harder, the design constraints are such that during the critical pre-charge window, FETs cannot be naively be placed in parallel. When operating in the linear regime, minute differences in device characteristics and temperature can cause drastic load imbalances. Thus controller design equations only allow parallel FETs for the purposes of increasing steady state on current, not increasing power or capacitance during the pre-charge window.

For a given FET, it will have a “safe operating area” plot like the below (this from the PSMN3R7-100BSE used in the soon-to-be-released power_dist r4):

This shows how much energy can be safety dissipated over different time windows. When designing the pre-charge system, it is then a balancing act of getting things to charge as quickly as possible, while not violating the SOA. Because of the details, going faster or slower can be problematic. Faster can be an issue because the energy may violate the short term peak energy, slower may be the culprit because the initial load current spends a longer time moving through the FET when it has a large voltage across it.

Next steps

In the next post, I’ll cover my various iterations, and where they fell short.

Next-gen power_dist (part 2)

Last time I covered the limitations of the power_dist r3.1, here I’ll cover some iterations of the design process.

My initial design goals for this version are based largely around improving the major limitations identified before:

  • Positive side switching: By switching the positive rail, a whole class of use failures is removed, as most people expect ground to be common throughout a system.
  • Increased voltage range: moteus r4.5 and the pi3hat both support 44V, so any new power_dist board should support at least that.
  • Lower quiescent current: Ideally, the quiescent current would be measured in microamps, or at least at a level that it does not confuse BMS systems.
  • Energy monitoring: Often in the development of the quad A1, I wanted to have a system level power and energy monitoring solution so as to identify the energy cost of various maneuvers and gaits. Tracking that at the power_dist level seems like a logical place.
  • Wider load envelope: The 3.1 version had a relatively limited maximum downstream capacitance and turn-on current draw. It was enough to power on 12 moteus controllers and a small computer, but not much else.

To achieve these goals, I decided to try using what is known as a “hot swap controller”. These are integrated circuits that are intended for use in cards that plug into server backplanes. Given that any given card could potentially have a large decoupling capacitance, inserting it live into a backplane could cause arcing, and high currents that cause the overall bus voltage to drop outside of tolerable limits.

While such controllers are intended to be used at the point of load, they are largely applicable in this centralized system too, where the “hot plugging” is of a battery instead of an individual load.

And here is where my design story becomes “complicated”. Because both I didn’t fully work through the consequences of each design approach, and because I was not familiar with the intrinsic limitations of hot swap controllers, I ended up taking a route that involved 4 different prototypes before I arrived at one that was mostly acceptable. Rather than go down that route linearly, in the next post I’ll instead distill what I learned about hot swap controllers and design before describing the final solution.

Development of next-gen power_dist (part 1)

The current iteration of the mjbots power_dist board released back in the summer of 2020 is pretty useful. It pre-charges the input, provides a soft switch, and gives you a bunch of output connectors to make wiring easier.

r3.1 Limitations

However, this version did have some limitations and potential problems. The first is that the pre-charge method it uses, a simple on/off pre-charge resistor, is unable to support a wide range of supply voltages. Either the resistor has a low value, in which case large input voltages will cause thermal failure, or for larger values, it isn’t able to actually pre-charge the bus sufficiently before engaging the primary MOSFET.

Secondly, it switches the negative rail. As pointed out in the documentation and by numerous YouTube commenters, if you are not careful, this can result in magic smoke being released if ground on the output and input is connected in any way.

Third, the protection afforded by the board is relatively limited. It merely performs the pre-charge function. It is still possible for short circuits or over-voltage events to result in damage to either downstream circuitry, or the upstream battery.

Fourth, the quiescent current is larger than I would like. At around 2-3 mA, it isn’t that large, but it means you can’t leave a battery connected for more than a day or so. Even worse, some BMS see that quiescent load as something they need to remain active for, which reduces standby battery life more substantially.

Looking forward

Given those shortcomings, I wanted to see if I could do better for the next revision. In the next several posts I’ll walk through my design process.

moteus position anti-windup

The moteus controller uses a somewhat unique integrated position / velocity / torque controller with per-command configurable proportional and derivative gains. Through various combinations of these settings, it can emulate many different types of controllers, but one that it has struggled with until now was a pure velocity controller.

It has been minimally possible to use moteus as a purely velocity controlled since wraparound support was implemented, but that came with a caveat. Either the proportional term needed to be set to 0, in which case velocity tracking performance was poor, or if the proportional term was non-zero, an external torque would cause the position to drift arbitrarily far from the target position. Then if the external torque were released, the controller would “catch up” for all the lost ground, moving very rapidly.

Now, however, as of release 2021-03-05, an optional configurable parameter has been added to the moteus firmware which enables the “config.max_position_slip” option. When this is finite, it acts as an anti-windup term on the position tracker, keeping it from getting far out of line. Tuning this lets you control how hard the controller tries to track velocity in the face of external disturbance, and how much catching up it will do when that external disturbance is relaxed.

This wasn’t conceptually hard to implement, but needed careful construction to interact properly with the existing stop position and position bounds that the firmware implements.

Here’s a video demonstrating the problem, and how the new configurable lets you resolve it:

pi3hat configurable CAN

To date, the pi3hat CAN channels only supported CAN properties suitable for use with moteus controllers. Given that’s what most people are using them for, that’s fine. However, there was no real constraint behind that, just laziness.

Thus, I’ve released new firmware for the pi3hat that supports configuring the bitrate, FD-ness, and other properties of all 5 CAN channels.

Currently only the C++ library exposes the configuration functionality, but it will be easy enough to add to python when someone needs it.