I’ve heard a lot about micropython (essentially python for microcontrollers) over the past couple of days and finally decided to try it out. My area of interest is ‘speed’ i.e. what does it take to get up and running with something beyond that of a typical embedded ‘hello world’ example. So, decided to give it a shot and use it for some real prototyping work — ‘Compare sensor fusion filters/algorithms’.
Here’s what I found out.
At the bare minimum, microcontroller programming involves at least 5 steps i.e.
- Write your code — its mostly C or some form of C (Arduino etc.)
- Compile your code for the specific microcontroller architecture (with a specific tool-chain for a given architectures — AVR, Xtensa, ARM etc.)
- Flash your code to the board with some usb-to-serial adapter.
- Debug your code with an IDE and a lot of serial log/print statements.
With micropython, you could strike out 2 (and a half) of the 5. The extra half (in the 2.5) is for a pretty good standard library.
- ESP32 board running micropython
- Jupyter Notebooks for flashing, debugging your code via the repl
- IMU (mpu6050) for my gyro, accelerometer readings
- Processing IDE for visualization
What surprised me the most was — I went from absolutely nothing to a working prototype in 45 minutes (of which 15 mins went into some tool research and installation). This is pretty crazy!!
- Read raw values from the IMU’s onboard (MEMS) gyroscope and accelerometer
- Do some math to convert raw readings — angular velocity and linear acceleration to actual degrees of rotation about a particular axis.
- Use the computed angles to simulate 3D rotation in the processing IDE.
- Accelerometer readings are pretty noisy but average out over longer timescales
- Gyroscope readings are pretty good over short durations but tend to drift due to integration of the ‘bias error’ (noise in sensor readings).
The problem of noisy readings can be addressed with ‘sensor fusion’ filters which involves the application of some control theory to fuse readings from multiple sensors. Without going into the nitty gritty technical details, 2 good ones are.
Complementary filter: This filter combines two sensor readings together in a way that produces the best estimate. The filter is as it simple as it gets (can be implemented as a single line of code).
In our case, this filter does exactly what is necessary, it favors the gyroscope readings for short time durations and the accelerometer’s average readings over a longer time duration. This is done by arriving at a cutoff % for trusting each sensor’s measurement.
In my case, cutoff is defined to be 0.90. This means that at every time step, 90% of the new angle measurement comes from the old angle measurement plus the integrated gyro measurement. The remaining 10% comes from the accelerometer. This slowly averages in the accelerometer over many time steps.
Kalman filter: This one’s a little more complex in terms of its implementation but follows the same logic — its great for dealing with sensor noise.
- We build a model of the system as a couple of equations (i.e. a probability distribution for the state of the system).
- Use this model to estimate or predict the next measurement which is again a probability distribution
- And lastly use our predicted measurement to balance/update the (actual) observed measurement.
The equations might look whacky but the theory is pretty intuitive. Here is a lovely write-up on the subject — https://www.bzarg.com/p/how-a-kalman-filter-works-in-pictures/
The code — https://github.com/nihalpasham/micropython_sensorfusion for
- Retrieving and filtering sensor readings from the IMU
- A simple processing sketch for 3D simulation.
- Python on bare metal with a REPL for microcontrollers is a phenomenal idea.
- Existing python code can be repurposed (for the most part including stuff like async)
- Micropython’s standard library includes a subset of the python library + additions for the embedded world.
- A bunch of options to maximize performance and deal with GC pauses.