This library provides a simple API that describes inertial measurement units. It implements various popular I2C and SPI IMUs behind that API. Callers of the library can rely on the API returning consistently typed data regardless of the choice of sensor.
This library provides an API to expose various implementations of gyroscopes, accelerometers, and magnetometers. It offers a suitable abstraction that presents the sensor data in a consistent way, so application authors need not worry about the implementation details of the sensors.
Users create a struct mgos_imu
object, to which they add sensors (gyroscope,
acellerometer and magnetometer) either via I2C or SPI. Some chips have all
three sensor types, some have only two (typically accelerometer and gyroscope)
and some have only one type. After successfully adding the sensors to the
mgos_imu
object, reads can be performed, mostly by performing
mgos_imu_*_get()
calls.
All implementations offer the calls described below in the IMU API
.
Some implementations offer chip-specific addendums, mostly setting sensitivity
and range options.
struct mgos_imu *mgos_imu_create()
-- This call creates a new (opaque) object
which represents the IMU device. Upon success, a pointer to the object will be
returned. If the creation fails, NULL is returned.
void mgos_imu_destroy()
-- This cleans up all resources associated with with
the IMU device. The caller passes a pointer to the object pointer. If any
sensors are attached (gyroscope, accelerometer, magnetometer), they will be
destroyed in turn using mgos_imu_*_destroy()
calls.
bool mgos_imu_read()
-- This call schedules all sensors attached to the IMU
to be read from. It is not generally necessary to call this method directly,
as the mgos_imu_*_get()
calls internally schedule reads from the sensors as
well.
bool mgos_imu_accelerometer_present()
-- This returns true
if the IMU has an
attached accelerometer sensor, or false
otherwise.
bool mgos_imu_gyroscope_present()
-- This returns true
if the IMU has an
attached gyroscope sensor, or false
otherwise.
bool mgos_imu_magnetometer_present()
-- This returns true
if the IMU has an
attached magnetometer, or false
otherwise.
For each of accelerometer, gyroscope and magnetometer, the following primitives exist:
bool mgos_imu_*_create_i2c()
-- This attaches a sensor to the IMU
based on the type
enum given, using the i2c
bus and specified i2caddr
address. The function will return true
upon success and false
in case
either detection of the sensor, or creation of it failed.
bool mgos_imu_*_create_spi()
-- This attaches a sensor to the IMU
based on the type
enum given, using the spi
bus and a specified cable select
in cs_gpio
. The function will return true
upon success and false
in case
either detection of the sensor or creation of it failed.
bool mgos_imu_*_destroy()
-- This detaches a sensor from the IMU if it exists.
It takes care of cleaning up all resources associated with the sensor, and
detaches it from the i2c
or spi
bus. The higher level mgos_imu_destroy()
call uses these lower level calls to clean up sensors as well.
bool mgos_imu_*_get()
-- This call returns sensor data after
polling the sensor for the data. It returns true
if the read succeeded, in
which case the floats pointed to by x
, y
and z
are filled in. If an
error occurred, false
is returned and the contents of x
, y
and z
are
unmodified. Note the units of the return values:
- magnetometer returns units of
Gauss
. - accelerometer returns units of
G
. - gyroscope returns units of
degrees per second
.
const char *mgos_imu_*_get_name()
-- This returns a symbolic name of the
attached sensor, which is guaranteed to be less than or equal to 10 characters
and always exist. If there is no sensor of this type attached, VOID
will be
returned. If the sensor is not known, UNKNOWN
will be returned. Otherwise,
the chip manufacturer / type will be returned, for example MPU9250
or
ADXL345
or MAG3110
.
bool mgos_imu_*_set_orientation()
and bool mgos_imu_*_get_orientation()
--
Often times a 9DOF sensor will have multiple chips, whose axes do not line up
correctly. Even within a single chip the accelerometer, gyroscope and
magnetometer axes might not line up (for an example of this, see MPU9250
chapter 9.1).
To ensure that the x
, y
, and z
axes on all sensors are pointed in the same
direction, we can set the orientation on the gyroscope and magnetometer.
See mgos_imu.h
for more details and an example of how to do this.
- MPU9250 and MPU9255
- ADXL345
- LSM303D and LSM303DLM
- MMA8451
- LSM9DS1
- LSM6DSL
- MPU6000 and MPU6050
- MPU6886 ("just works")
- ICM20948
- MPU9250 and MPU9255
- L3GD20 and L3GD20H
- ITG3205
- LSM9DS1
- LSM6DSL
- MPU6000 and MPU6050
- MPU6886 (work in progress)
- ICM20948
- MAG3110
- AK8963 (as found in MPU9250/MPU9255)
- AK8975
- BMM150
- LSM303D and LSM303DLM
- HMC5883L
- LSM9DS1
- ICM20948
This is a fairly straight forward process, taking the ADXL345
accelerometer
as example:
- Add a new driver header and C file, say
src/mgos_imu_adxl345.[ch]
. The header file exposes all of the#define
's for registers and constants. - In the header file, declare a
detect
,create
,destroy
andread
function.bool mgos_imu_adxl345_detect()
-- this function optionally attempts to detect the chip, often times by reading a register or set of registers which uniquely identifies it. Not all chips can actually be detected, so it's OK to not define this function at all.bool mgos_imu_adxl345_create()
-- this function has to perform the initialization of the chip, typically by setting the right registers and possibly creating a driver-specific memory structure (for, say, coefficients or some such). If used, that memory structure is attached to theuser_data
pointer, and if so, the implementation of the_destroy()
function must clean up and free this memory again.bool mgos_imu_adxl345_read()
-- this function performs the chip specific read functionality. This will be called whenever the user asks for data, either by callingmgos_imu_read()
or by callingmgos_imu_*_get()
.bool mgos_imu_adxl345_destroy()
-- this function deinitializes the chip, and optionally clears and frees the driver-specific memory structure inuser_data
. Not all chips need additional memory structures or deinitialization code, in which case it's OK to not define this function at all.
- Implement the
detect
,create
,destroy
andread
functions in the source code filesrc/mgos_imu_adxl345.c
. - Add the device to one of the
enum mgos_imu_*_type
ininclude/mgos_imu.h
. In the example case of adxl345 (which is an accelerometer), add it toenum mgos_imu_acc_type
. - Add a string version of this to function
mgos_imu_*_get_name()
so that callers can resolve the sensor to a human readable format. Make sure that the string name is not greater than 10 characters. - Add the type to the
switch()
inmgos_imu_*_create_i2c()
(or_spi()
) functions. - Update this document to add the driver to the list of supported drivers.
- Test code on a working sample, and send a PR using the guidelines laid out in contributing.
It is important to note a few implementation rules when adding drivers:
- New drivers MUST NOT change any semantics of the abstraction layer
(
mgos_imu
andmgos_imu_*
members) nor themgos_imu_*_get()
functions. - Implementations of drivers MUST provide bias, scaling and other
normalization in the driver itself. What this means, in practice, is that
the correct units must be produced (
m/s/s
,deg/s
anduT
). - Pull Requests MUST NOT mix driver and abstraction changes. Separate them.
- Changes to the abstraction layer MUST be proven to work on all existing drivers, and will generally be scrutinized.
Here's an example, for the magnetometer chip AK8975
, showing a set of commits
for each of the steps above (and honoring the driver implementation rules).
- Add
src/mgos_imu_ak8975.[ch]
commit - Extend
enum mgos_imu_mag_type
ininclude/mgos_imu.h
commit - Add to
mgos_imu_magnetometer_get_name()
insrc/mgos_imu_magnetometer.c
commit - Add to
mgos_imu_magnetometer_create_i2c()
insrc/mgos_imu_magnetometer.c
commit
An example program that creates an IMU of type MPU9250
(which has an
accelerometer, a gyroscope and a magnetometer all in one tiny package):
#include "mgos.h"
#include "mgos_i2c.h"
#include "mgos_imu.h"
static void imu_cb(void *user_data) {
struct mgos_imu *imu = (struct mgos_imu *)user_data;
float ax, ay, az;
float gx, gy, gz;
float mx, my, mz;
if (!imu) return;
if (mgos_imu_accelerometer_get(imu, &ax, &ay, &az))
LOG(LL_INFO, ("type=%-10s Accel X=%.2f Y=%.2f Z=%.2f",
mgos_imu_accelerometer_get_name(imu), ax, ay, az));
if (mgos_imu_gyroscope_get(imu, &gx, &gy, &gz))
LOG(LL_INFO, ("type=%-10s Gyro X=%.2f Y=%.2f Z=%.2f",
mgos_imu_gyroscope_get_name(imu), gx, gy, gz));
if (mgos_imu_magnetometer_get(imu, &mx, &my, &mz))
LOG(LL_INFO, ("type=%-10s Mag X=%.2f Y=%.2f Z=%.2f",
mgos_imu_magnetometer_get_name(imu), mx, my, mz));
}
enum mgos_app_init_result mgos_app_init(void) {
struct mgos_i2c *i2c = mgos_i2c_get_global();
struct mgos_imu *imu = mgos_imu_create();
struct mgos_imu_acc_opts acc_opts;
struct mgos_imu_gyro_opts gyro_opts;
struct mgos_imu_mag_opts mag_opts;
if (!i2c) {
LOG(LL_ERROR, ("I2C bus missing, set i2c.enable=true in mos.yml"));
return false;
}
if (!imu) {
LOG(LL_ERROR, ("Cannot create IMU"));
return false;
}
acc_opts.type = ACC_MPU9250;
acc_opts.scale = 16.0; // G
acc_opts.odr = 100; // Hz
if (!mgos_imu_accelerometer_create_i2c(imu, i2c, 0x68, &acc_opts))
LOG(LL_ERROR, ("Cannot create accelerometer on IMU"));
gyro_opts.type = GYRO_MPU9250;
gyro_opts.scale = 2000; // deg/sec
gyro_opts.odr = 100; // Hz
if (!mgos_imu_gyroscope_create_i2c(imu, i2c, 0x68, &gyro_opts))
LOG(LL_ERROR, ("Cannot create gyroscope on IMU"));
mag_opts.type = MAG_AK8963;
mag_opts.scale = 12.0; // Gauss
mag_opts.odr = 10; // Hz
if (!mgos_imu_magnetometer_create_i2c(imu, i2c, 0x0C, &mag_opts))
LOG(LL_ERROR, ("Cannot create magnetometer on IMU"));
mgos_set_timer(1000, true, imu_cb, imu);
return MGOS_APP_INIT_SUCCESS;
}
For a cool demo, take a look at my Demo Apps:
This project is not an official Google project. It is not supported by Google and Google specifically disclaims all warranties as to its quality, merchantability, or fitness for a particular purpose.