IMU filtering on an STM32 + MPU6000
#hardwareFor making balancing robots (disclaimer: not my robot), you certainly need a good sense of the robot’s orientation in the world. I had a disastrous experience a few months ago of trying to use a $1500+ IMU which breaks if your robot moves too fast. Like, 300 degrees/sec, which is nothing for a relatively small, agile robot.
After that fiasco was remedied by inventing the World’s Stupidest IMU™—a laser-cut pulley and rubber band device that surely deserves a post for itself—I decided to upgrade from the $1500 sensor to a $5 MPU6000.
This meant doing the filtering on a microcontroller, which was an exciting prospect with the DSP/FPU hardware on the STM32F373 I’ve been using.
Math libraries
The CMSIS DSP libraries have special Cortex-M4 functions like fast trigonometric, FFT and matrix functions, but thankfully, some testing showed that embedded Eigen does matrix multiplies and inverses just as fast as the CMSIS DSP functions (at least, with -O3 -ffast-math
), meaning I could use the more convenient syntax without fear of being slow.
Filtering
The IMU combines an accelerometer (which intuitively tells you—only if the robot is moving very slowly—which direction gravity is, using which you can back out the orientation):
\[\widehat r_{\mathrm{acc}} = \tan^{-1} (a_x, a_z) , ~~~~~ \widehat p_{\mathrm{acc}} = -\tan^{-1} (a_y, a_z)\]and a (rate) gyroscope whose readings can be integrated to also have a horribly drifty estimate of orientation. There are numerous articles online which describe how the CF makes the best of high-frequency and low-frequency responses of the gyroscope and accelerometer, but the way in which they are combined
\[\widehat r = \lambda \widehat r_{\mathrm{acc}} + (1 - \lambda) \widehat r_{\mathrm{gyro}}\]should make anyone uneasy. The roll and pitch don’t lie on a linear space, and this addition to interpolate doesn’t make much sense. The quaternion-EKF which I adopted from Gareth Cross solves two of the problems with the CF by
- switching to a non-singular representation (quaternions), and
- using a more principled filtering technique (intuitively keeping track of a belief over the best state to explain the measurements, versus ad-hoc combining the disparate state estimates).
Experiments
I did the experiments on one of my own “mainboards” (boards that basically have all the things I think I’ll need so I don’t have to make a new one for every project):
For each of the plots, the blue trace is the complementary filter and the green trace is the EKF. I sort of tuned them both to have similar squiggliness (that’s the statistically correct way to do this, right?) in this “control” test:
Slow movements—pitch followed by roll
I moved the board back and forth first about the pitch axis, then the roll axis:
The results here are as expected, and already we see that the EKF’s use of a singularity-free representation means that it can avoid the complementary filter’s pitch estimate mishap at ~90 degrees of roll.
Rapid motions
Things get more interesting here, and (while there’s no ground truth here–sorry on vacation–you’ll just have to believe that I was spinning the board back and forth at ~6 Hz) it looks like the CF “gives up” at times where the EKF is still tracking the oscillations.
Three full rotations in roll
The last test I did was just do 3 full rotations in roll, and while the roll traces look OK (there’s some nonsense and jittering happening around the wrap-around because I insisted that the quaternion be converted back to an euler angle) the pitch on the CF is very mysterious indeed. It keeps jumping from 0 to \(\pm\pi\)!
Timing results
Other than the evidence that the EKF’s estimate itself is superior, the last important matter is the computation time (see below for the full code):
Thanks, SPI and hardware floating-point! This means that it should be possible to do closed-loop feedback control at almost 5 KHz even with the EKF!
Now to actually use this on the robots…
Code
The entire code to do this experiment (using the koduino libraries) is: