/upy-motion

A simple MPU6050 driver written in micropython

Primary LanguagePythonMIT LicenseMIT

upy-motion

An MPU6050 driver written in micropython. This driver should be compatible with any micropython device. This driver does not support quaternion

Features:

  1. Auto-calibration if the ofs argument is omitted
  2. After auto-calibration the ofs argument is supplied to you
  3. Automatic FIFO if an interrupt pin and callback are supplied
  4. Kalman and complimentary filters are built in and automatically applied to data based on flags you set
  5. Data can be retrieved as raw gyroscope and accelerometer data or as angles (roll, pitch only)
  6. Temperature can be retrieved as Celsius or Fahrenheit
  7. Numerous print options available that format the data into a very neat and easy-to-read display
  8. Self-test is built in and using just one property will tell you if your device is functioning properly
  9. Everything you can do is well-documented below

Community:

To officially file a bug report or feature request you can use these templates: bug report | feature request

To discus features, bugs or share your own project that utilize code in this repo: join the discussion




Ports:

MPU6050.py

This can be uploaded directly to the board, but is intended to be used as a frozen module. For information regarding how to setup the sdk and freeze a module you can refer to this post on the Raspberry Pi forum.

MPU6050.mpy

This is a cross-compiled version of MPU6050.py. It is intended to be uploaded to your board as you would any normal .py script.




Docs:

MPU6050(bus, sda, scl, ofs, intr, callback, angles, clock, gyro, accel, dlpf, rate, filtered, anglefilter, R, Q, A, addr, freq)

The argument for this constructor may seem daunting, but it can be broken up into sections that make it far easier to manage. From bus through scl sets up I2C connection with the device. ofs is the configuration offsets that help your device function accurately. Examples are provided later in this document that explain how to get this value. From intr through angles are interrupt related and only used if you want to use FIFO. From clock through rate are device specific settings. From filtered through A are filter specific settings. addr and freq are the device address and the frequency it should run at. There is very little reason why you should ever have to change these, which is why they are the very last arguments.

Most things are evidenced later in this document, but there are a couple of things that are easier to simply explain right here. Providing an interrupt pin and a callback will automatically trigger the script to use FIFO. The rate argument has 2 purposes. Whatever value you supply will be the divisor for gyroscope clock output, which is it's main intended purpose. However, my driver also uses half of this number to determine how many samples to average when using a complementary filter.

Arg Type Description Default
bus int I2C bus id REQUIRED
sda int or Pin data pin REQUIRED
scl int or Pin clock pin REQUIRED
ofs tuple axis offsets None
intr int or Pin interrupt pin (FIFO only) None
callback function function to call on interrupt (FIFO only) None
angles int return angles instead of data (FIFO only) False
clock int clock source to use CLK_PLL_XGYRO
gyro int gyroscope full scale range GYRO_FS_500
accel int accelerometer full scale range ACCEL_FS_2
dlpf int digital low-pass filter DLPF_BW_188
rate int sample rate 4
filtered int which properties to filter NONE
anglefilter int which filters to apply to angles NONE
R float Kalman filter measure 0.003
Q float Kalman filter bias 0.001
A float complementary filter alpha .8
addr int device I2C address 0x68
freq int I2C frequency 400000



Constants:


clock

Possible values for the clock constructor argument are the following. The default clock is CLK_PLL_XGYRO. The documents recommend that you use one of the gyro clocks. All clocks (except external) have their typical frequency listed. Actual frequency may vary +/- 3 Khz.

Const Value Frequency
CLK_INTERNAL 0x00 8 Mhz
CLK_PLL_XGYRO 0x01 33 Khz
CLK_PLL_YGYRO 0x02 30 Khz
CLK_PLL_ZGYRO 0x03 27 Khz
CLK_PLL_EXT32K 0x04 32.768 Khz
CLK_PLL_EXT19M 0x05 19.2 Mhz
CLK_KEEP_RESET 0x07 0

gyro

Possible values for the gyro constructor argument are the following. The default gyro is GYRO_FS_500.

Const Value
GYRO_FS_250 0x00
GYRO_FS_500 0x01
GYRO_FS_1000 0x02
GYRO_FS_2000 0x03

accel

Possible values for the accel constructor argument are the following. The default accel is ACCEL_FS_2

Const Value
ACCEL_FS_2 0x00
ACCEL_FS_4 0x01
ACCEL_FS_8 0x02
ACCEL_FS_16 0x03

dlpf

Possible values for the dlpf constructor argument include the following. The default dlpf is DLPF_BW_188. Headers marked ms below represent the milliseconds of delay a DLPF will create.

Const Value Accel(ms) Gyro(ms) FS (Khz)
DLPF_BW_256 0x00 0 0.98 8
DLPF_BW_188 0x01 2.0 1.9 1
DLPF_BW_98 0x02 3.0 2.8 1
DLPF_BW_42 0x03 4.9 4.8 1
DLPF_BW_20 0x04 8.5 8.3 1
DLPF_BW_10 0x05 13.8 13.4 1
DLPF_BW_5 0x06 19.0 18.6 1

filtered

Possible values for the filtered constructor argument include the following. The default filter is NONE. Applying one or more of these flags tells the driver which data to filter. For accel and gyro Kalman filters will be applied. For angles another flag must be used to determine which filters you want applied.

Flag Value
NONE 0x00
FILTER_ACCEL 0x01
FILTER_GYRO 0x02
FILTER_ANGLES 0x04
FILTER_ALL 0x07

anglefilter

Possible values for the anglefilter constructor argument include the following. The default anglefilter is NONE.

Flag Value
NONE 0x00
ANGLE_KAL 0x01
ANGLE_COMP 0x02
ANGLE_BOTH 0x03



Properties:


.device_id

The id of the device


.connected

True or False device is connected


.data

Returns gyroscope and accelerometer data. This data may be filtered with a Kalman filter if the appropriate flag is supplied to the filtered argument in the constructor. The filters section contains more information on how to use filters. This is a namedtuple with the following fields

Field Type Description
.acc_x float accelerometer x
.acc_y float accelerometer y
.acc_z float accelerometer z
.gyro_x float gyroscope x
.gyro_y float gyroscope y
.gyro_z float gyroscope z

.angles

Returns angles concocted from accelerometer data. These angles may be filtered (with Kalman, complementar or both) according to the flag supplied for the anglefilter argument in the constructor. The filters section contains more information on how to use filters. This is a namedtuple with the following fields

Field Type Description
.roll float roll angle
.pitch float pitch angle

.int_angles

Returns angles concocted from accelerometer data as ints. These angles may be filtered (with Kalman, complementar or both) according to the flag supplied for the anglefilter argument in the constructor. The filters section contains more information on how to use filters. This is a namedtuple with the following fields

Field Type Description
.roll int roll angle
.pitch int pitch angle

.passed_self_test

True or False passed system self-test


.celsius

Returns the temperature in Celsius


.fahrenheit

Returns the temperature in Fahrenheit




Methods:


.start()

If an interrupt pin and callback were supplied to the constructor, this will start FIFO interrupts


.stop()

If an interrupt pin and callback were supplied to the constructor, this will stop FIFO interrupts


.print_data()

Prints the gyroscope and accelerometer data with any flagged filters automatically applied. The filters section contains more information on how to use filters.


.print_from_data(data:tuple)

Prints the gyroscope and accelerometer data that was passed to it


.print_angles(asint:bool=False)

Prints the roll and pitch angles with any flagged filters automatically applied. If asint is True angles will be gathered from .int_angles. The filters section contains more information on how to use filters.


.print_from_angles(angles:tuple)

Prints the angle data that was passed to it


.print_celsius()

Prints the temperature in Celsius


.print_fahrenheit()

Prints the temperature in Fahrenheit


.print_offsets()

Prints the offsets as a line of code to be used as the ofs argument when instantiating MPU6050


.print_all()

Prints everything except offsets




Usage

All the below examples can be copy/pasted, but you must make sure to provide the bus, sda and scl pins (or pin numbers) that apply to your wiring scheme. If an interrupt pin or calibration offsets are used in an example, those too must be replaced with the data that applies to you.


calibration

The very first thing you should do is calibrate the device. When it completes it will print a small line of code that you need to copy and paste for use with the ofs argument. Failure to provide an ofs argument will result in your device auto-calibrating every time you instance it. Make sure your device is as flat and level as you can get it before running calibration. Only run calibration from a fresh power-up of the device. If you do a good job calibrating, the numbers this returns can be used constantly, and you should not need to calibrate again unless you customize any of the device configuration arguments (ie. clock, dlpf, rate, gyro or accel). If you do intend to change any of the configuration arguments, you should add those changes to the script below before running it.

from mpu6050 import MPU6050

MPU6050(1, 6, 7)

FIFO

Supplying an interrupt pin and a callback will trigger the driver to use FIFO automatically. You must also call start() for interrupts to begin. The data argument in the handler will contain all of the accelerometer and gyroscope data, unless you set the angles constructor argument to True, in which case data will then contain angles values.

from mpu6050 import MPU6050

def handler(data:tuple):
    if 'mpu' in globals():
        print('[{:<16}] {:<10.2f}'.format('TEMPERATURE', mpu.fahrenheit))
        mpu.print_from_data(data)

mpu = MPU6050(1, 6, 7, (1314, -1629, 410, 28, -17, 51), 2, handler)
if mpu.passed_self_test:
    mpu.start()

polling

If an interrupt pin and callback are not supplied the driver assumes you want to manage your own polling

from mpu6050 import MPU6050
import utime

mpu = MPU6050(1, 6, 7, (1314, -1629, 410, 28, -17, 51))

if mpu.passed_self_test:
    while True:
        print('[{:<16}] {:<10.2f}'.format('TEMPERATURE', mpu.fahrenheit))
        mpu.print_data()
        utime.sleep_ms(100)

accessing data

data is a namedtuple and can be used like any other namedtuple. You can either unpack the properties or use them directly. The below examples illustrate both of these methods.

from mpu6050 import MPU6050
import utime

mpu = MPU6050(1, 6, 7, (1314, -1629, 410, 28, -17, 51))

if mpu.passed_self_test:
    while True:
        ax, ay, az, gx, gy, gz = mpu.data

or

from mpu6050 import MPU6050

def handler(data:tuple):
    if 'mpu' in globals():
        asum = data.acc_x  + data.acc_y  + data.acc_z
        gsum = data.gyro_x + data.gyro_y + data.gyro_z

mpu = MPU6050(1, 6, 7, (1314, -1629, 410, 28, -17, 51), 2, handler)
if mpu.passed_self_test:
    mpu.start()

angles

angles are handled no different than data

from mpu6050 import MPU6050

def handler(data:tuple):
    if 'mpu' in globals():
        roll, pitch = mpu.angles

mpu = MPU6050(1, 6, 7, (1314, -1629, 410, 28, -17, 51), 2, handler)
if mpu.passed_self_test:
    mpu.start()

just like data, angles has its own print method, as well

from mpu6050 import MPU6050
import utime

mpu = MPU6050(1, 6, 7, (1314, -1629, 410, 28, -17, 51))

if mpu.passed_self_test:
    while True:
        mpu.print_angles()
        utime.sleep_ms(100)

when using fifo you can tell the script to send angles instead of axis data to the handler callback by setting the angles constructor argument to True

from mpu6050 import MPU6050

def handler(data:tuple):
    if 'mpu' in globals():
        roll, pitch = data
        mpu.print_from_angles(data)

mpu = MPU6050(1, 6, 7, (1314, -1629, 410, 28, -17, 51), 2, handler, True)
if mpu.passed_self_test:
    mpu.start()

filters

This driver supports 2 different types of filters (Kalman and complementary). Complementary filters can only be applied to angles. If a complementary filter is flagged on angles it will return the average of all the samples taken. The amount of samples that are taken will be half of the rate argument that was supplied to the constructor.

from mpu6050 import MPU6050, FILTER_GYRO, FILTER_ANGLES, ANGLE_COMP

def handler(data:tuple):
    if 'mpu' in globals():
        mpu.print_from_angles(data)
        
cfg = dict(
    rate        = 20,                          #MPU6050_SPLRTDIV ~ comp filter samples at half of this number
    filtered    = FILTER_GYRO | FILTER_ANGLES, #wont filter accelerometer raw readings
    anglefilter = ANGLE_COMP,                  #apply only complementary filter to angles
    angles      = True                         #send data to handler as angles
)
mpu = MPU6050(1, 6, 7, (1368, -1684, 416, 20, -6, 49), 2, handler, **cfg)

if mpu.passed_self_test:
    mpu.start()