Meet the newest revision of the moteus controller!
Yes, it does look mostly the same as the r4.3 that has been getting a lot of use lately. This revision exists mostly to improve manufacturability, but I snuck in a minor design improvement while at it. Now, the maximum voltage input is rated up to 44V from the 34V of the r4.3! (Note though, that the pi3hat and power_dist still are limited to 34V). Otherwise the new controller is fully electrically, mechanically, and software compatible with the r4.3.
The moteus controller has always supported multiple turns when counting positions. It has a one-revolution magnetic encoder built in, but after turn on, it keeps track of how many turns have occurred. However, if you’ve followed previous moteus tutorials, you have probably noticed a persistent caveat that for accurate control, the position of the output shaft needs to stay within a hundred revolutions of 0.0 or so. Now, I’ll describe why that was, and what I’ve done to remove the limitation, allowing unlimited rotations!
The moteus controller uses a somewhat unique integrated position / velocity / torque control loop. This formulation gives a couple of advantages: First, there is no bandwidth loss due to having a cascaded position and velocity controller. Second, when driven by a higher level controller, it can seamlessly switch between position, velocity, and torque control, or any combination of them without having to manage mode transitions.
The command consists of the following values, all as 32 bit floating point values (optionally upscaled from integer values using the register protocol).
The control loop measures two quantities as input, the “current position” and the “current velocity”. The position is measured as a 32 bit signed integer, where one revolution of the magnetic encoder equals 65536 counts. The velocity is numerically differentiated across the most recent 6.4ms of movement.
There are two internal state variables as well: One is the “target position”. This captures the most recent position command, and is advanced by the velocity command at the full control rate. The other is the integrative term of the PID controller. Both of these are stored as 32 bit floating point values.
This structure poses a few inherent limitations. One, being that as the control position is sent as a floating point value, the resolution available for positioning decreases as you get further from 0. That probably isn’t a big limitation, as there aren’t many applications where you want to have both absolute positions and also unlimited revolutions.
The bigger limitation is in the “target position” internal state variable. It needs to be updated to take into account the current velocity command at every control cycle, or 1/40000 of a second. For a commanded speed of 0.01 revolutions per second, this incremental update is only 2.5e-7 of a revolution. Given that 32 bit floating point values only have roughly 7 decimal digits of mantissa available to them, you don’t have to get far beyond 0 before an update that small doesn’t even change the value at all.
The command format also has an option, such that if the command position is set to a floating point NaN value, it will “capture” the current position. This can be used to command velocity-only control with an implicit integrative term, or when combined with a stop position to move to a target at a fixed velocity. However, since “capturing” stores the value as a floating point value, significant precision can be lost. This was only a problem at larger position values, but at the maximum position before wraparound, the available capture resolution was measured in multiple degrees.
The resolution was relatively straightforward. Instead of storing the “target position” as a floating point value, it is now stored as a 64 bit integer measured in 1/(2**32) of a magnetic encoder revolution. This gives sufficient precision to represent velocities as small as 0.0001Hz (0.036 dps) uniformly at all positions, while still having more absolute range than the measured current position value. The final PID controller is then expressed relative to the target position. This lets it still operate in floating point coordinates, but with no worry about large artifacts due to a position offset.
The only other implementation hurdle was making it run fast enough. Largely that revolved around ensuring there was never a need to convert between 64 bit integers and floating point values, which is relatively slow on the STM32G4.
With this fix in place, it is possible to operate the controller safely at high velocities for arbitrary periods of time. Even when the “current position” value wraps around from positive to negative! Also, low speed control works just as well at any position offset. When operating in those “continuous rotation” applications, the user should just be careful about if the “desired position” field of the command should be set. Largely, it should be left as NaN for when used in continuous rotation applications.
Here’s a video showing high speed wraparound and low speed at arbitrary offsets.
At the request of @nichols in discord, I’ve recently implemented a new control mode in the moteus controller, “stay within”. In this mode, as long as the controller is inside the currently commanded bounds, only a feedforward torque is commanded. When either of the optional lower or upper bound is violated, the normal PID controller is used to force the position back to the bound.
Here’s a quick video demo:
Note that this could have been roughly accomplished in a couple of ways by a higher level controller — either by monitoring the position and commanding zero kp/kd scales when inside the boundary, or just solely commanding feedforward torques based on position sensing. However, this approach lets the control run at the full 40kHz of the moteus controller, which results in much smoother operation at the boundary condition.
The moteus controller uses a DRV8323 smart driver IC to drive the power MOSFETs as well as provide various safety functions. One of the capabilities it has which has so far been unexplored in moteus is its ability to control the drive strength and dead time through software configuration.
In a switching power supply or switching motor inverter, MOSFETs are arranged in a half bridge configuration. Depending upon the type of converter, one or more half bridges are used (3 phase inverters like moteus use 3 of them). Each “half bridge” has two MOSFETs, one connected between positive power and the output terminal, and the other connected between the output terminal and ground.
Power MOSFETs typically have relatively large gate capacitance, so to change their state quickly requires a lot of current. Additionally, you never want both MOSFETs conducting at the same time, otherwise current would flow straight from the supply to ground, called “shoot through”. Thus the driver has a configurable “dead time” which enforces that both are off for at least that long when switching (currents flow through the body diodes of the MOSFETs during this state).
Selecting these parameters is a balancing act. If the drive current is too low, the MOSFETs take a long time to turn on and off, which means they spend more time in a high resistance state. At some point however, higher drive currents don’t make the MOSFETs switch any faster, and just burn more power in the driver without any benefit. Similarly for the dead time, if it is too low this will result in shoot through, and if too high, it will result in current flowing through the body diodes for longer, which is much less efficient.
Until now, I hadn’t done any real optimization of these parameters, aside from ensuring the system was functional within a safety margin. In advance of some other to be announced developments, I decided to take another look.
To make a test, I set up a moteus controller with a test motor, but set up so that there was no thermal connection between the motor and the controller, and that the controller was not heatsinked at all. That would allow me to more easily determine how much heat was coming from the controller itself. Then, for various supply voltages, I commanded a fixed D phase current with just enough Q phase voltage so that the motor gently spun around. This ensured that all 3 of the half bridges were used equally. Then, I waited until things had reached thermal equilibrium, and used my DIY thermal board inspector, to measure the temperature of the motor windings, FETs, and the DRV8323.
With that test methodology in hand, I was able to search and locate the optimal drive strength, and discovered that I can use the smallest available dead time with no problems.
DRV8323 @ 24V / 8A phase current
DRV8323 @ 32V / 8A phase current
FETs @ 24V / 8A phase current
FETS @ 32V / 8A phase current
Change in thermal equilibrium with no heatsinking
So, a nice win, especially at higher input voltages. The updated settings are in git master now, and will soon be in a new release.
This release has a number of minor improvements in the host tools (for which there continue to be no distributed binaries, you get to build from source). The biggest improvement in the firmware is the improved low-torque operation as documented here and here. If you have any questions or want help upgrading, hop into discord at #moteus and ask!
Here, the test script is relatively simple. I have the “fixture” controller sweep at a very low velocity (0.01Hz) through a bit more than one full revolution using a relatively high I term in the PID controller to ensure that it really holds that position no matter what external torque is applied. Then, the “device under test” controller is just commanded either to be powered off, or in position mode with a pd gain of 0 and a feedforward torque. Then I can just measure the result from the torque transducer while this sweeps through a full revolution, and correlate the measured torque with the encoder position.
For these plots, I subtracted out the DC offset to just concentrate on the ripple and not scaling factor errors. Here’s the plot from the pre-fixes controller:
In these plots, the higher torque values are displayed in lighter colors to make it easier to see the values at low torque. Also, the standard deviation and peak-to-peak values are rendered in the lower right hand corner. Before any fixes, when the controller was off (i.e. completely unpowered), there was about 0.026Nm of peak-to-peak ripple, however, when turning on the controller and commanding 0 torque, that jumped up to a 0.047 Nm peak-to-peak.
Now for the post-fix plot:
Post fixes, the torque ripple at all the tested torque levels is nearly indistinguishable from the torque of the completely unpowered motor, which is what we want to see!
Recently dlickindorf pointed out in the mjbots discord that he was having problems with very low torques on his large PMSM hobby-grade motor. While moteus doesn’t have any anti-cogging support yet, it should still be capable of driving motors such that the unexpected torque isn’t much worse than the baseline cogging torque of the motor. However, he was seeing much worse behavior with controlling to 0 current, as much as a full percent of the maximum torque of the motor.
Needless to say, I wanted to try and understand the behavior and figure out how to make it better. I was able to reproduce the problem locally, so that was a good start. It was easiest to see when commanding a position with a low maxmum torque. Say on a 8108 motor, with 0.05 N*m maximum torque, the cogging-like torque was high enough that the position control could get stuck in a local minima far from the control point.
In other experiments with higher torque, the switching noise because audibly “squirrely” at certain electrical theta angles. One often occurred at a 0 theta pre-magnetic offset compensation, and another appeared when one phase current was near maximum and the other two phase currents were opposite sign but near equal magnitude.
When the moteus controller is calibrated, it calculates an offset between the electrical commutation angle and the magnetic encoder not just as an average, but at 64 separate points in the revolution of the magnetic encoder. This allows the calibration to take out some of the effect of magnets that are not perfectly axis-aligned with the encoder.
However, when calculating the moving average of the offset value, I neglected to take into account the fact that the encoder wraps around between 0 and 2*pi, and those ends of the offset curve were smoothed independently. If the calibration was somewhat steep in that particular window, it resulted in a discontinuity in the offset curve, which resulted in high frequency noise when controlling near that position.
That problem was relatively easy to solve, by just filtering in a circular domain.
The next issue was dealing where the phase current became obviously very noisy. Ultimately I tracked this down to a combination of mis-configuration of the STM32G4 ADC peripheral and coupling between the digital and analog domains.
The latter is the easiest to cover. In the core FOC loop, I use a debugging pin that goes high during the loop to be able to easily identify how long it takes on an oscilloscope. The problem was that I was starting the ADC conversions, then immediately switching that pin. That results in a large enough current transient either within the chip or on the PCB that the analog samples have significant noise introduced. Solving that was easy enough by just moving the debug pin assignment to after the ADC samples are completed.
The former was a result of bugs introduced when I ported the moteus code from the STM32F4 to the G4. The ADC peripherals are significantly different in the G4 (more like the F3 peripherals) and the configuration registers are almost entirely different. At one point several months ago I was trying to configure the sample time for the ADCs but not actually doing so at all. When I resolved that issue, I didn’t look at the resulting sampled waveforms very closely and didn’t notice that the current waveforms were still pretty bad. This was compounded by a further issue. The moteus has 3 current shunts, but it was only sampling two of them and inferring the 3rd by Kirchoff’s law.
Here’s a plot showing the 3 current values as the voltage phase was advanced through a few cycles:
The biggest problem here was that the ADC prescaler was too low for the system clock I was running. Once fixing the prescaler, and making a few additional changes, the current waveforms now look like:
So yes, there is now much less unusual discretization going on across all 3 current channels.
The moteus controller, uses an absolute magnetic encoder to sense the position of the rotor and thus be capable of field oriented control FOC of brushless motors. To date, all the iterations of the controller have used the AS5047P encoder from ams. This is relatively common, works fine over SPI and hasn’t caused any problems. While investigating some other issues, I decided to take a stab at trying some alternate encoders. First, I tried the AS5047U, which is the same basic encoder, but incorporates a digital filter. I also tried the MA732, from Monolithic Power, which uses a different operating principle and also includes a digital filter. The plus side of the MA732 is that it reports full 16 bit values, even if not all of them provide a lot of value.
Testing the MA732 wasn’t as easy as the AS5047U, since it doesn’t have the same pinout as the AS5047 family. Fortunately, the footprint is smaller, so I was able to make my first “castellated” adapter board:
It is from oshpark — I had to hand dremel the edges to get all the vias exposed. Since it was a one-off, I didn’t worry too much about getting perfect alignment when soldering it up. Here’s a microscope image of the side with the best alignment.
And finally with the MA732 installed:
It worked the first time, although I had to tweak the code slightly to use the alternate SPI protocol of the MA732. I’ll write up the results in a later post.
This has the torque transducer on one end, coupled to the “fixture” moteus controller through a bearing support. Then that is connected via a 3d printed coupler to the “device under test” moteus controller, which is hard mounted to the base plate. Any net torque between the two controllers will be coupled back to the transducer resulting in a measured torque.
I mounted it all to a nicely machined fixture plate I got from ebay. It’s flexible enough that I can build one for the qdd100 on it as well, although I might just get an entire second setup so that I can leave them both assembled all the time.