Anton Bondarenko
Created May 15, 2021

Dynamics Tracking Device for Outdoor Activities

Track your detailed dynamics when trailing, hiking, cycling or exercising.

51
Dynamics Tracking Device for Outdoor Activities

Things used in this project

Hardware components

nRF5340 Development Kit
Nordic Semiconductor nRF5340 Development Kit
×1
SparkFun IMU Breakout - MPU-9250
SparkFun IMU Breakout - MPU-9250
×1
Power Profiler Kit II
Nordic Semiconductor Power Profiler Kit II
×1
Case G1389G (Gainta Industries Ltd. )
×1
Outdoor chest pouch (Generic)
×1

Software apps and online services

nRF Connect SDK
Nordic Semiconductor nRF Connect SDK
SEGGER Embedded Studio

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

Case Gainta g1389 Drawing

Schematics

PPK II connection

How to connect PPK II

MPU-9250 Connection

How to connect SparkFun MPU-9250 Breakout

nRF5340 connection

How to connect nRF5340

Code

mpu9250.h

C Header File
MPU9250 driver header
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 * Adapted from drivers for MPU6050 and 
 * MPU 9150 by Intel Corporation (c) 2016
 */

#ifndef ZEPHYR_DRIVERS_SENSOR_MPU9250_MPU9250_H_
#define ZEPHYR_DRIVERS_SENSOR_MPU9250_MPU9250_H_

#include <device.h>
#include <drivers/gpio.h>

struct mpu9250_raw_data{
		int16_t A_x;
		int16_t A_y;
		int16_t A_z;
		int16_t T;
		int16_t G_x;
		int16_t G_y;
		int16_t G_z;
		int16_t A_x_off;
		int16_t A_y_off;
		int16_t A_z_off;
		int16_t A_fs;
		int16_t G_x_off;
		int16_t G_y_off;
		int16_t G_z_off;
		int16_t G_fs;
};
typedef struct mpu9250_raw_data mpu9250_raw_data;
//#define MPU9250_ACC_OFFSET_SCALE 9.7656E-4f

struct mpu9250_data {
	const struct device *i2c;
	const struct device *ak8963;
	struct mpu9250_raw_data raw_data;
#ifdef CONFIG_MPU9250_TRIGGER
	const struct device *dev;
	const struct device *gpio;
	struct gpio_callback gpio_cb;
	struct sensor_trigger data_ready_trigger;
	sensor_trigger_handler_t data_ready_handler;
	struct k_work work;
#endif
};

struct mpu9250_config {
	const char *i2c_label;
	uint16_t i2c_addr;
#ifdef CONFIG_MPU9250_TRIGGER
	uint8_t int_pin;
	uint8_t int_flags;
	const char *int_label;
#endif
};

#ifdef CONFIG_MPU9250_TRIGGER
int mpu9250_trigger_set(const struct device *dev,
			const struct sensor_trigger *trig,
			sensor_trigger_handler_t handler);
int mpu9250_init_interrupt(const struct device *dev);
#endif

#endif // ZEPHYR_DRIVERS_SENSOR_MPU9250_MPU9250_H_

mpu9250_ext.h

C Header File
Additional kernel-space header with driver interface extensions for MPU9250. Put into the .../ncs/zephyr/include/drivers/sensor folder.
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef MPU9250_EXT_H
#define MPU9250_EXT_H

#include <drivers/sensor.h>
#include <sys/util.h>

// Sensor specific channels and attributes
enum mpu9250_channel {
    MPU9250_CHAN_ACCEL = SENSOR_CHAN_ACCEL_XYZ,
    MPU9250_CHAN_GYRO = SENSOR_CHAN_GYRO_XYZ,
    MPU9250_CHAN_TEMP = SENSOR_CHAN_DIE_TEMP,
    MPU9250_CHAN_CONTROL = SENSOR_CHAN_PRIV_START,
    MPU9250_CHAN_END
};
enum mpu9250_attribute {
    MPU9250_ATTR_RANGE = SENSOR_ATTR_FULL_SCALE,
	MPU9250_ATTR_SLEEP = SENSOR_ATTR_PRIV_START,
    MPU9250_ATTR_WAKEUP,
    MPU9250_ATTR_CLOCK_SOURCE,
    MPU9250_ATTR_CLOCK_DIVIDER,
    MPU9250_ATTR_BANDWIDTH,
    MPU9250_ATTR_RESOLUTION,
    MPU9250_ATTR_MODE,
    MPU9250_ATTR_I2C_MODE,
    MPU9250_ATTR_END
};

// System Clock
enum mpu9250_mode { 
    MPU9250_WAKEUP,
    MPU9250_SLEEP 
};
typedef enum mpu9250_mode mpu9250_mode;

// System Clock
enum mpu9250_clock_source { 
    MPU9250_INTERNAL, 
    MPU9250_AUTO 
};
typedef enum mpu9250_clock_source mpu9250_clock_source;

// System Clock Divider
enum mpu9250_clock_divider { 
    MPU9250_1000,
    MPU9250_500,
    MPU9250_333,
    MPU9250_250,
    MPU9250_200,
    MPU9250_167,
    MPU9250_143,
    MPU9250_125,
    MPU9250_111,
    MPU9250_100 
};
typedef enum mpu9250_clock_divider mpu9250_clock_divider;

// MPU9250 I2C mode
enum mpu9250_i2C_mode { 
    MPU9250_BYPASS = 2,
};
typedef enum mpu9250_i2C_mode mpu9250_i2c_mode;

// Gyroscope range
enum mpu9250_gyro_range { MPU9250_G250DPS,
                          MPU9250_G500DPS,
                          MPU9250_G1000DPS,
                          MPU9250_G2000DPS };
typedef enum mpu9250_gyro_range mpu9250_gyro_range;
#define MPU9250_GYRO_RESOLUTION (float[]){7.6294E-3f, \
                                          1.5259E-2f, \
                                          3.0518E-2f, \
                                          6.1036E-2f}

// Gyroscope bandwidth:                     BW    D     FS BW_T D_T
enum mpu9250_gyro_fchoice { MPU9250_FC11 = 0U,   // see dlpf
                            MPU9250_FCX0 = 1U,   // 8800  0.064 32 4000 0.04
                            MPU9250_FC01 = 2U }; // 3600  0.11  32 4000 0.04
typedef enum mpu9250_gyro_fchoice mpu9250_gyro_fchoice;

// Gyroscope low-pass filter            //  BW   D    FS BW_T D_T
enum mpu9250_gyro_dlpf { MPU9250_GLPF_250HZ,    //  250  0.97 8  4000  0.04
                         MPU9250_GLPF_184HZ,    //  184  2.9  1   188  1.9
                         MPU9250_GLPF_92HZ,     //   92  3.9  1    98  2.8
                         MPU9250_GLPF_41HZ,     //   41  5.9  1    42  4.8
                         MPU9250_GLPF_20HZ,     //   20  9.9  1    20  8.3
                         MPU9250_GLPF_10HZ,     //   10 17.85 1    10 13.4
                         MPU9250_GLPF_5HZ,      //    5 33.48 1     5 18.6
                         MPU9250_GLPF_3600HZ }; // 3600  0.17 8  4000  0.04
typedef enum mpu9250_gyro_dlpf mpu9250_gyro_dlpf;

// Accelerometer range
enum mpu9250_accel_range { MPU9250_A2G,
                           MPU9250_A4G,
                           MPU9250_A8G,
                           MPU9250_A16G };
typedef enum mpu9250_accel_range mpu9250_accel_range;                          
#define MPU9250_ACCEL_RESOLUTION (float[]){ 6.1035E-5f, \
                                            1.2207E-4f, \
                                            2.4414E-4f, \
                                            4.8828E-4f }

// Accelerometer bandwidth:  FC         DLPF BW    D     FS
enum mpu9250_accel_fchoice { MPU9250_FC0,   //  x    1130  0.75  4
                             MPU9250_FC1 }; //  See below
typedef enum mpu9250_accel_fchoice mpu9250_accel_fchoice;

// Accelerometer LPF DLPF      BW         D      FS
enum mpu9250_accel_dlpf { MPU9250_ALPF_460HZ_0,  //  1.94  1
                          MPU9250_ALPF_184HZ,  //  5.80  1
                          MPU9250_ALPF_92HZ,   //  7.80  1 
                          MPU9250_ALPF_41HZ,   // 11.80  1
                          MPU9250_ALPF_20HZ,   // 19.80  1
                          MPU9250_ALPF_10HZ,   // 35.70  1
                          MPU9250_ALPF_5HZ,    // 66.96  1
                          MPU9250_ALPF_460HZ_1 };//  1.94  1
typedef enum mpu9250_accel_dlpf mpu9250_accel_dlpf;

// Termometer resolution and offset
#define MPU9250_TEMP_SENSITIVITY 333.87f 
#define MPU9250_TEMP_ROOM_OFFSET 0.0f
#define MPU9250_TEMP_OFFSET 21.0f

#endif // MPU9250_EXT_H

mpu9250.c

C/C++
MPU9250 driver
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 * Adapted from drivers for MPU6050 and 
 * MPU 9150 by Intel Corporation (c) 2016
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT invensense_mpu9250

#include <kernel.h>
#include <drivers/sensor.h>
#include <drivers/i2c.h>
#include <sys/byteorder.h>
#include <sys/util.h>
#include <drivers/sensor/mpu9250_ext.h>
#include "mpu9250.h"

#include <logging/log.h>
LOG_MODULE_REGISTER(MPU9250, CONFIG_SENSOR_LOG_LEVEL);

static int pow2small(unsigned pow) {
	int res = 1;
	for (unsigned i = 0; i < pow; i++)
		res *= 2;
	return res;
}

static int mpu9250_sleep(const struct device *mpu){
	const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x6B, BIT(6), BIT(6));
	if (rc != 0) {
		LOG_ERR("Failed to send MPU9250 to sleep.");
		return rc;
	}
	return 0;
}

static int mpu9250_wakeup(const struct device *mpu){
        const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x6B, BIT(6), 0U);
	if (rc != 0) {
		LOG_ERR("Failed to wake up MPU9250.");
		return rc;
	}
	return 0;
}

static int mpu9250_set_mode(const struct device *mpu, 
                            mpu9250_mode m){
    const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x6B, BIT(6), m << 6);
	if (rc != 0) {
		LOG_ERR("Failed to set MPU9250 mode.");
		return rc;
	}
	return 0;
}

static int mpu9250_set_clock_source(const struct device *mpu, 
                             mpu9250_clock_source cs){
    const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x6B, BIT_MASK(3), cs);
	if (rc != 0) {
		LOG_ERR("Failed to set MPU9250 clock source.");
		return rc;
	}
	return 0;
}

static int mpu9250_set_clock_divider(const struct device *mpu, 
                                     mpu9250_clock_divider cd){
    const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x19, BIT_MASK(3), cd);
	if (rc != 0) {
		LOG_ERR("Failed to set MPU9250 clock divider.");
		return rc;
	}
	return 0;
}

static int mpu9250_set_i2c_mode(const struct device *mpu, 
                                mpu9250_i2c_mode m){
    const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x37, BIT(1), m);
	if (rc != 0) {
		LOG_ERR("Failed to set MPU9250 clock source.");
		return rc;
	}
	return 0;
}

static int mpu9250_set_gyro_range(const struct device *mpu, 
                           mpu9250_gyro_range r){
          struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x1A, BIT(3) | BIT(4), r);
	if (rc != 0) {
		LOG_ERR("Failed to set gyroscope range.");
		return rc;
	}
	mpu_data->raw_data.G_fs = r;
	return 0;
}

static int mpu9250_set_gyro_bandwidth(const struct device *mpu, 
                               		  mpu9250_gyro_dlpf dlpf, 
                               		  mpu9250_gyro_fchoice fchoice){
    const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x1A, BIT_MASK(2), fchoice);
	if (rc != 0) {
		LOG_ERR("Failed to set gyroscope bandwidth.");
		return rc;
	}
	rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x1C, BIT_MASK(3), dlpf);
	if (rc != 0) {
		LOG_ERR("Failed to set gyroscope bandwidth.");
		return rc;
	}
	return 0;
}

static int mpu9250_set_accel_range(const struct device *mpu, 
                            	   mpu9250_accel_range r){
          struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x1B, BIT(3) | BIT(4), r);
	if (rc != 0) {
		LOG_ERR("Failed to set accelerometer range.");
		return rc;
	}
	mpu_data->raw_data.A_fs = r;
	return 0;
}

static int mpu9250_set_accel_bandwidth(const struct device *mpu, 
                              		   mpu9250_accel_dlpf dlpf, 
                              		   mpu9250_accel_fchoice fchoice){
    const struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
	int rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
								 0x1C, BIT_MASK(3), dlpf);
	if (rc != 0) {
		LOG_ERR("Failed to set accelerometer bandwidth.");
		return rc;
	}
	rc = i2c_reg_update_byte(mpu_data->i2c, 
	        			         mpu_cfg->i2c_addr, 
						     0x1C, BIT(3), fchoice);
	if (rc != 0) {
		LOG_ERR("Failed to set accelerometer bandwidth.");
		return rc;
	}
        return 0;
}

static int mpu9250_sample_fetch(const struct device *mpu,
								enum sensor_channel chan) {								
	      struct mpu9250_data *mpu_data = mpu->data;
	const struct mpu9250_config *mpu_cfg = mpu->config;
				 mpu9250_raw_data *frame = &mpu_data->raw_data;
	int16_t buf[7];
	int rc = i2c_burst_read(mpu_data->i2c, mpu_cfg->i2c_addr,
			   				0x3B, (uint8_t *)buf, 14);
	if (rc != 0) {
		LOG_ERR("Failed to read data sample.");
		return rc;
	}
	frame->A_x = sys_be16_to_cpu(buf[0]);
	frame->A_y = sys_be16_to_cpu(buf[1]);
	frame->A_z = sys_be16_to_cpu(buf[2]);
	frame->T = sys_be16_to_cpu(buf[3]);
	frame->G_x = sys_be16_to_cpu(buf[4]);
	frame->G_y = sys_be16_to_cpu(buf[5]);
	frame->G_z = sys_be16_to_cpu(buf[6]);
	return rc;
}

static int mpu9250_channel_get(const struct device *mpu,
			       			   enum sensor_channel chan,
			                   struct sensor_value *val) {								   
	struct mpu9250_raw_data *frame = &((struct mpu9250_data *)mpu->data)->raw_data;
	switch (chan) {
		case SENSOR_CHAN_ACCEL_X:
			val->val1 = frame->A_x /*- frame->A_x_off * 16 / pow2small(frame->A_fs)*/;
			val->val2 = 0;
			break;
		case SENSOR_CHAN_ACCEL_Y:
			val->val1 = frame->A_y /*- frame->A_y_off * 16 / pow2small(frame->A_fs)*/;
			val->val2 = 0;
			break;
		case SENSOR_CHAN_ACCEL_Z:
			val->val1 = frame->A_z /*- frame->A_z_off * 16 / pow2small(frame->A_fs)*/;
			val->val2 = 0;
			break;
		case SENSOR_CHAN_GYRO_X:
			val->val1 = frame->G_x /*- frame->G_x_off * 4 / pow2small(frame->G_fs)*/;
			val->val2 = 0;
			//val->val2 = -frame->G_x_off * 4 % pow2small(frame->G_fs) * 1000000;
			break;
		case SENSOR_CHAN_GYRO_Y:
			val->val1 = frame->G_y /*- frame->G_y_off * 4 / pow2small(frame->G_fs)*/;
			val->val2 = 0;
			//val->val2 = -frame->G_y_off * 4 % pow2small(frame->G_fs) * 1000000;
			break;
		case SENSOR_CHAN_GYRO_Z:
			val->val1 = frame->G_z /*- frame->G_z_off * 4 / pow2small(frame->G_fs)*/;
			val->val2 = 0;
			//val->val2 = -frame->G_z_off * 4 % pow2small(frame->G_fs) * 1000000;
			break;
		case SENSOR_CHAN_DIE_TEMP:
			val->val1 = frame->T;
			val->val2 = 0;
			break;
		default: // Any other channel - unsupported
			LOG_WRN("Unsuported channel.");
			val->val1 = 0;
			val->val2 = 0;
	}
	return 0;	
}

static int mpu9250_attr_set(const struct device *mpu,
				 			enum sensor_channel schan,
				 			enum sensor_attribute sattr,
				 			const struct sensor_value *val){
	int rc;
	enum mpu9250_channel chan = (enum mpu9250_channel)schan;
	enum mpu9250_attribute attr = (enum mpu9250_attribute)sattr;
	switch (chan) {
		case MPU9250_CHAN_CONTROL:
			if (attr == MPU9250_ATTR_SLEEP) {
				rc = mpu9250_sleep(mpu);
			}
			else {
				if (attr == MPU9250_ATTR_WAKEUP) {
					rc = mpu9250_wakeup(mpu);
				}
				else {
					if (attr == MPU9250_ATTR_CLOCK_SOURCE) {
						rc = mpu9250_set_clock_source(mpu, val->val1);
					}
					else {
						if(attr == MPU9250_ATTR_CLOCK_DIVIDER) {
							rc = mpu9250_set_clock_divider(mpu, val->val1);
						}
						else {
							if (attr == MPU9250_ATTR_MODE) {
								rc = mpu9250_set_mode(mpu, val->val1);
							}
							else {
								if(attr == MPU9250_ATTR_I2C_MODE) {
									rc = mpu9250_set_i2c_mode(mpu, val->val1);
								}
								else {
									LOG_WRN("Unexpected device attribute");
									rc = EINVAL;
								}
							}
						}	
					}
				}	
			}
			break;
		case MPU9250_CHAN_GYRO:
			if (attr == MPU9250_ATTR_RANGE) {
				rc = mpu9250_set_gyro_range(mpu, val->val1);
			}
			else {
				if (attr == MPU9250_ATTR_BANDWIDTH){
					rc = mpu9250_set_gyro_bandwidth(mpu, val->val1, val->val2);
				}
				else {
					LOG_WRN("Unexpected device attribute");
					rc = EINVAL;
				}
			}
			break;
		case MPU9250_CHAN_ACCEL:
			if (attr == MPU9250_ATTR_RANGE) {
				rc = mpu9250_set_accel_range(mpu, val->val1);
			}
			else {
				if (attr == MPU9250_ATTR_BANDWIDTH){
					rc = mpu9250_set_accel_bandwidth(mpu, val->val1, val->val2);
				}
				else {
					LOG_WRN("Unexpected device attribute");
					rc = EINVAL;
				}
			}
			break;	
	/*	case MPU9250_CHAN_TEMP:
			if (attr == MPU9250_ATTR_RESOLUTION) {
				rc = mpu9250_set_temp_resolution_offset(mpu, val->val1, val->val2);
			}
			else {
				LOG_WRN("Unexpected device attribute");
				rc = EINVAL;
			}
			break;	*/
		default: // Any other channel - unsupported
			LOG_WRN("Unsuported channel.");
			rc = EINVAL;
	}
	return rc;	
}

static const struct sensor_driver_api mpu9250_driver_api = {
#if CONFIG_MPU9250_TRIGGER
	.trigger_set = mpu9250_trigger_set,
#endif
	.sample_fetch = mpu9250_sample_fetch,
	.channel_get = mpu9250_channel_get,
	.attr_set = mpu9250_attr_set,
};

int mpu9250_init(const struct device *dev)
{
	      struct mpu9250_data *drv_data = dev->data;
	const struct mpu9250_config *cfg = dev->config;
				 mpu9250_raw_data *frame = &drv_data->raw_data;
	drv_data->i2c = device_get_binding(cfg->i2c_label);
	if (drv_data->i2c == NULL) {
		LOG_ERR("Failed to get pointer to %s device", cfg->i2c_label);
		return -EINVAL;
	}
	int rc;
	// Wakeup chip
	rc = i2c_reg_update_byte(drv_data->i2c, cfg->i2c_addr, 0x6B, BIT(6), 0);
	if (rc != 0) {
		LOG_ERR("Failed to awake MPU9250.");
		return rc;
	} 
	// Check chip ID 
	uint8_t id;
	rc = i2c_reg_read_byte(drv_data->i2c, cfg->i2c_addr, 0x75, &id);  
	if (rc != 0) {
		LOG_ERR("Failed to read chip ID.");
		return rc;
	}
	if (id != 0x71) {
		LOG_ERR("Invalid chip ID: %X.", id);
		return -EINVAL;
	}
	// Wait for initialization of the registers
	k_sleep(K_MSEC(300));
	// Read factory gyroscope offset data
	int16_t buf[3];
	rc = i2c_burst_read(drv_data->i2c, cfg->i2c_addr,
			   				0x13, (uint8_t *)buf, 6);
	if (rc != 0) {
		LOG_ERR("Failed to read gyroscope calibration data.");
		return rc;
	}
	frame->G_x_off = sys_be16_to_cpu(buf[0]);
	frame->G_y_off = sys_be16_to_cpu(buf[1]);
	frame->G_z_off = sys_be16_to_cpu(buf[2]);
	// Read factory accelerometer offset data
	rc = i2c_burst_read(drv_data->i2c, cfg->i2c_addr,
			   				0x77, (uint8_t *)buf, 6);
	if (rc != 0) {
		LOG_ERR("Failed to read accelerometer calibration data.");
		return rc;
	}
	frame->A_x_off = sys_be16_to_cpu(buf[0]);
	frame->A_y_off = sys_be16_to_cpu(buf[1]);
	frame->A_z_off = sys_be16_to_cpu(buf[2]);
	// Enable MPU9250 pass-through to have access to ak8963 
	// [TBD] Make it configurable behavior
	rc = i2c_reg_update_byte(drv_data->i2c, cfg->i2c_addr,
				0x37, BIT(1), BIT(1));
	if (rc != 0) {
		LOG_ERR("Failed to enable pass-through mode for MPU9250.");
		return rc;
	}
	// Link AK8963
/*	const char * const label1 = DT_LABEL(DT_INST(0, asahi_kasei_ak8963));
	const struct device *ak8963 = device_get_binding(label1);
	if (!ak8963) {
		LOG_ERR("Failed to find sensor %s", label1);
	}
	drv_data->ak8963 = ak8963; */
	// Send chips to sleep
	rc = i2c_reg_update_byte(drv_data->i2c, cfg->i2c_addr, 0x6B, BIT(6), BIT(6));
	if (rc != 0) {
		LOG_ERR("Failed to send MPU9250 to sleep.");
		return rc;
	} 
	else
		LOG_DBG("MPU9250 is ready."); 
#ifdef CONFIG_MPU9250_TRIGGER
	if (mpu9250_init_interrupt(dev) < 0) {
		LOG_ERR("Failed to initialize interrupts.");
		return -EIO;
	}
	else { 
		LOG_DBG("Interrupt initialized successfully.");
	}
#endif
	return 0;
}

static struct mpu9250_data data;
static const struct mpu9250_config config = {
	.i2c_label = DT_INST_BUS_LABEL(0),
	.i2c_addr = DT_INST_REG_ADDR(0),
#ifdef CONFIG_MPU9250_TRIGGER
	.int_pin = DT_INST_GPIO_PIN(0, int_gpios),
	.int_flags = DT_INST_GPIO_FLAGS(0, int_gpios),
	.int_label = DT_INST_GPIO_LABEL(0, int_gpios),
#endif // CONFIG_MPU9250_TRIGGER 
};

DEVICE_DT_INST_DEFINE(0, mpu9250_init, device_pm_control_nop, &data, &config, 
                      POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &mpu9250_driver_api);

mpu9250_trigger.c

C/C++
MPU9250 Interrupt handler (part of еру driver)
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 * Adapted from drivers for MPU6050 and 
 * MPU 9150 by Intel Corporation (c) 2016
 */

#include <drivers/i2c.h>
#include <sys/util.h>
#include <kernel.h>
#include <drivers/sensor.h>
#include <device.h>
#include <logging/log.h>
#include <drivers/gpio.h>

#include "mpu9250.h"

LOG_MODULE_DECLARE(MPU9250, CONFIG_SENSOR_LOG_LEVEL);

// Method of MPU9250 driver API: sensor_trigger_set(...)
// Invoked from the user application
int mpu9250_trigger_set(const struct device *dev,
			const struct sensor_trigger *trig,
			sensor_trigger_handler_t handler) {
	struct mpu9250_data *drv_data = dev->data;
	const struct mpu9250_config *cfg = dev->config;
	if (trig->type != SENSOR_TRIG_DATA_READY) {
		return -ENOTSUP;
	}
	gpio_pin_interrupt_configure(drv_data->gpio, cfg->int_pin,
				     GPIO_INT_DISABLE);
	drv_data->data_ready_handler = handler;
	if (handler == NULL) {
		return 0;
	}
	drv_data->data_ready_trigger = *trig;
	gpio_pin_interrupt_configure(drv_data->gpio, cfg->int_pin,
				     GPIO_INT_EDGE_TO_ACTIVE);
	return 0;
}

// Callback initialization. Invoked on sensor initialization
// during system start from sensor initialization procedure.
static void mpu9250_gpio_callback(const struct device *dev,
				  struct gpio_callback *cb, uint32_t pins)
{
	struct mpu9250_data *drv_data =
		CONTAINER_OF(cb, struct mpu9250_data, gpio_cb);
	const struct mpu9250_config *cfg = drv_data->dev->config;
	ARG_UNUSED(pins);
	gpio_pin_interrupt_configure(drv_data->gpio, cfg->int_pin,
				     GPIO_INT_DISABLE);
	k_work_submit(&drv_data->work);
}

// Handler invoked from handler proxy. Invokes the actual
// handler for the actual trigger set by user application.
static void mpu9250_thread_cb(const struct device *dev)
{
	struct mpu9250_data *drv_data = dev->data;
	const struct mpu9250_config *cfg = dev->config;
	if (drv_data->data_ready_handler != NULL) {
		drv_data->data_ready_handler(dev,
					     &drv_data->data_ready_trigger);
	}
	gpio_pin_interrupt_configure(drv_data->gpio, cfg->int_pin,
				     GPIO_INT_EDGE_TO_ACTIVE);
}

// Handler proxy (?). Invoked by system queue processor
// on work execution start. Set on interrupt setup.
static void mpu9250_work_cb(struct k_work *work){
	struct mpu9250_data *drv_data =
		CONTAINER_OF(work, struct mpu9250_data, work);
	mpu9250_thread_cb(drv_data->dev);
}

// Interrupt setup procedure. Invoked by sensor driver
// setup procedure on system start.
int mpu9250_init_interrupt(const struct device *mpu)
{
	struct mpu9250_data *drv_data = mpu->data;
	const struct mpu9250_config *cfg = mpu->config;

	// setup data ready gpio interrupt 
	drv_data->gpio = device_get_binding(cfg->int_label);
	if (drv_data->gpio == NULL) {
		LOG_ERR("Failed to get pointer to %s device",
			    cfg->int_label);
		return -EINVAL;
	}
	
	drv_data->dev = mpu;

	gpio_pin_configure(drv_data->gpio, cfg->int_pin,
			   GPIO_INPUT | cfg->int_flags);
	gpio_init_callback(&drv_data->gpio_cb,
			   mpu9250_gpio_callback,
			   BIT(cfg->int_pin));
	if (gpio_add_callback(drv_data->gpio, &drv_data->gpio_cb) < 0) {
		LOG_ERR("Failed to set gpio callback");
		return -EIO;
	}
	
	// Enable data ready interrupt
	if (i2c_reg_write_byte(drv_data->i2c, cfg->i2c_addr,
			       0x38, BIT(0)) < 0) {
		LOG_ERR("Failed to enable data ready interrupt.");
		return -EIO;
	}

	drv_data->work.handler = mpu9250_work_cb;

	gpio_pin_interrupt_configure(drv_data->gpio, cfg->int_pin,
				     GPIO_INT_EDGE_TO_ACTIVE);

	return 0;
}

Kconfig

INI
Kconfig for MPU9250 driver
# MPU9250 Nine-Axis Motion Tracking device configuration options
# This file describes options for MPU9050 and for MPU6050 inside;
# See the configuration for built-in AK8963
# Copyright (c) 2021 Anton Bondarenko
# SPDX-License-Identifier: Apache-2.0

menuconfig MPU9250
	bool "MPU9250 Nine-Axis Motion Tracking Device"
	depends on I2C
	help
	  Enable driver for MPU9250 I2C-based nine-axis motion tracking device.

if MPU9250

config MPU9250_TRIGGER
	bool "MPU9250 Interrupt"
	depends on GPIO
	help
		Enable interrupt-based pull mode
endif # MPU9250

CMakeLists.txt

INI
CMake input file for MPU9250 driver
# SPDX-License-Identifier: Apache-2.0

zephyr_library()

zephyr_library_sources_ifdef(CONFIG_MPU9250 mpu9250.c)
zephyr_library_sources_ifdef(CONFIG_MPU9250_TRIGGER mpu9250_trigger.c)

ak8963.h

C Header File
AK8963 driver header
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 * Based on driver for AK8975 by 
 * Copyright (c) 2016 Intel Corporation
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef ZEPHYR_DRIVERS_SENSOR_AK8963_AK8963_H_
#define ZEPHYR_DRIVERS_SENSOR_AK8963_AK8963_H_

#include <device.h>

struct ak8963_raw_data{
		int16_t M_x;
		int16_t M_y;
		int16_t M_z;
		int8_t M_x_off;
		int8_t M_y_off;
		int8_t M_z_off;
		bool valid;
};
typedef struct ak8963_raw_data ak8963_raw_data;

struct ak8963_data {
	const struct device *i2c;
	struct ak8963_raw_data raw_data;
};

struct ak8963_config {
	const char *i2c_label;
	uint16_t i2c_addr;
};

#endif // ZEPHYR_DRIVERS_SENSOR_AK8963_AK8963_H_ 

ak8963_ext.h

C Header File
Additional kernel-space header with driver interface extensions for AK8963. Put into the .../ncs/zephyr/include/drivers/sensor folder.
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef AK8963_EXT_H
#define AK8963_EXT_H

#include <drivers/sensor.h>
#include <drivers/sensor/mpu9250_ext.h>

// Sensor specific channels
enum ak8963_channel {
    AK8963_CHAN_CONTROL = SENSOR_CHAN_PRIV_START,
    AK8963_CHAN_MAGN = SENSOR_CHAN_MAGN_XYZ,
    AK8963_CHAN_MAGN_RAW_X = MPU9250_CHAN_END,
    AK8963_CHAN_MAGN_RAW_Y,
    AK8963_CHAN_MAGN_RAW_Z,
};

// Sensor specific attributes
enum ak8963_attribute {
    AK8963_ATTR_RESOLUTION = MPU9250_ATTR_RESOLUTION,
    AK8963_ATTR_MODE = MPU9250_ATTR_MODE,
};

// Magnetometer resolution:
enum ak8963_mag_resolution { AK8963_14B,
                             AK8963_16B };
typedef enum ak8963_mag_resolution ak8963_mag_resolution;                       
#define AK8963_MAG_RESOLUTION (float[]){ 5.8594E-1f, \
                                          1.4648E-1f }

// Magnetometer mode of operations:
enum ak8963_mode { AK8963_MODE_DOWN   = 0U,  // 0000 Power-down mode
                   AK8963_MODE_SNGL   = 1U,  // 0001 Single measurement mode
                   AK8963_MODE_8HZ    = 2U,  // 0010 Continuous measurement mode 1 (8Hz)
                   AK8963_MODE_100HZ  = 6U,  // 0110 Continuous measurement mode 2 (100Hz)
                   AK8963_MODE_TRIG   = 4U,  // 0100 External trigger measurement mode
                   AK8963_MODE_TEST   = 8U,  // 1000 Self-test mode
                   AK8963_MODE_FUSE   = 15U };  // 1111 Fuse ROM access mode
typedef enum ak8963_mode ak8963_mode;

#define AK8963_MAX_READING_ATTEMPTS 10000

#endif // AK89863_EXT_H

ak8963.c

C/C++
AK8963 driver
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 * Based on driver for AK8975 by
 * Copyright (c) 2016 Intel Corporation
 * SPDX-License-Identifier: Apache-2.0
 */

#define DT_DRV_COMPAT asahi_kasei_ak8963

#include <kernel.h>
#include <drivers/sensor.h>
#include <drivers/i2c.h>
#include <sys/util.h>
#include <drivers/sensor/ak8963_ext.h>
#include "ak8963.h"

#include <logging/log.h>
LOG_MODULE_REGISTER(AK8963, CONFIG_SENSOR_LOG_LEVEL);

static int ak8963_set_mag_resolution(const struct device *ak, 
                               		 ak8963_mag_resolution res){
    const struct ak8963_data *ak_data = ak->data;
	const struct ak8963_config *ak_cfg = ak->config;
	int rc = i2c_reg_update_byte(ak_data->i2c, 
	        			         ak_cfg->i2c_addr, 
								 0x0A, BIT(4), res << 4);
	if (rc != 0) {
		LOG_ERR("Failed to set magnetometer resolution.");
		return rc;
	}
	return 0;
}

static int ak8963_set_mag_mode(const struct device *ak, 
						 	   ak8963_mode mode){
    const struct ak8963_data *ak_data = ak->data;
	const struct ak8963_config *ak_cfg = ak->config;
	int rc = i2c_reg_update_byte(ak_data->i2c, 
	        			         ak_cfg->i2c_addr, 
								 0x0A, BIT_MASK(4), mode);
	if (rc != 0) {
		LOG_ERR("Failed to set magnetometer mode.");
		return rc;
	}
	return 0;
}

static int ak8963_sample_fetch(const struct device *ak,
							   enum sensor_channel chan) {
	      struct ak8963_data *ak_data = ak->data;
	const struct ak8963_config *ak_cfg = ak->config;
				 ak8963_raw_data *frame = &ak_data->raw_data;
	int16_t buf[3];
	uint8_t reg;
	int rc;
	bool dnrdy = true;
	bool ovrlfl = false;
	uint32_t count = AK8963_MAX_READING_ATTEMPTS;
	while (dnrdy && count > 0) {
		rc = i2c_reg_read_byte(ak_data->i2c, ak_cfg->i2c_addr, 0x02, &reg);
		if (rc != 0) {
			LOG_ERR("Failed to read magnetometer status.");
			return rc;
		}					   
		dnrdy = !(reg & BIT(0));
		ovrlfl = (reg & BIT(1));
		if(dnrdy) {
			LOG_DBG("Magnetometer data not ready, attemp # %d",
			AK8963_MAX_READING_ATTEMPTS - count + 1);
			k_sleep(K_MSEC(1));	
			rc = i2c_reg_read_byte(ak_data->i2c, ak_cfg->i2c_addr,
			   				   0x09, &reg);
			if (rc != 0) {
				LOG_ERR("Failed to clean magnetometer status.");
				return rc;
			}		
		}
		if(ovrlfl) { // We don't clean status as it will be cleaned on reading
			LOG_WRN("Magnetometer overrun, attemp # %d",
			AK8963_MAX_READING_ATTEMPTS - count + 1);
		}
		count--;
	}
	if(!count) { 
		LOG_WRN("Magnetometer excided the limit of reading attempts.");
		return EBUSY;
	}
	uint8_t dreg = 0x03;
	for(unsigned i = 0; i < 6; i++) {
		rc = i2c_reg_read_byte(ak_data->i2c, ak_cfg->i2c_addr,
			   				   dreg++, &(((uint8_t *)buf)[i]));
		if (rc != 0) {
			LOG_ERR("Failed to read data sample.");
			return rc;
		}
		rc = i2c_reg_read_byte(ak_data->i2c, ak_cfg->i2c_addr,
			   				   0x09, &reg);
		if (rc != 0) {
			LOG_ERR("Failed to read magnetometer status.");
			return rc;
		}
		rc = 0;
		
		if(reg & BIT(3)) { 
			LOG_WRN("Magnetometer overflow.");
		}
		if(/* ovrlfl || */ reg & BIT(3)) {
			rc = EINVAL;
			frame->valid = false;
		}
		else {
			rc = 0;
			frame->valid = true;
		}
	
	}
	frame->M_x = buf[0];
	frame->M_y = buf[1];
	frame->M_z = buf[2];
	return rc;
}

static int ak8963_channel_get(const struct device *ak,
			       			   enum sensor_channel chan,
			                   struct sensor_value *val) {								   
	struct ak8963_data *ak_data = ak->data;
	       ak8963_raw_data *frame = &ak_data->raw_data;
	switch (chan) {
		case SENSOR_CHAN_MAGN_XYZ:
			val->val1 = (int32_t)frame->valid;
			val->val2 = 0; 
			break;
		case SENSOR_CHAN_MAGN_X:
			val->val1 = frame->M_x + frame->M_x * (frame->M_x_off - 128) / 256;
			val->val2 = frame->M_x * (frame->M_x_off - 128) * 1000000 % 256 / 256; 
			break;
		case SENSOR_CHAN_MAGN_Y:
			val->val1 = frame->M_y + frame->M_y * (frame->M_y_off - 128) / 256;
			val->val2 = frame->M_y * (frame->M_y_off - 128) * 1000000 % 256 / 256; 
			break;
		case SENSOR_CHAN_MAGN_Z:
			val->val1 = frame->M_z + frame->M_z * (frame->M_z_off - 128) / 256;
			val->val2 = frame->M_z * (frame->M_z_off - 128) * 1000000 % 256 / 256; 
			break;
		case AK8963_CHAN_MAGN_RAW_X:
			val->val1 = frame->M_x;
			val->val2 = 0; 
			break;
		case AK8963_CHAN_MAGN_RAW_Y:
			val->val1 = frame->M_y;
			val->val2 = 0; 
			break;
		case AK8963_CHAN_MAGN_RAW_Z:
			val->val1 = frame->M_z;
			val->val2 = 0; 
			break;	
		default: // Any other channel - unsupported
			LOG_WRN("Unsuported channel.");
	}
	return 0;	
}

static int ak8963_attr_set(const struct device *ak,
				 		   enum sensor_channel schan,
				 		   enum sensor_attribute sattr,
				 		   const struct sensor_value *val){
	int rc = 0;
	enum ak8963_channel chan = (enum ak8963_channel)schan;
	enum ak8963_attribute attr = (enum ak8963_attribute)sattr;
	switch (chan) {
		case AK8963_CHAN_CONTROL:
			if (attr == AK8963_ATTR_MODE) {
				rc = ak8963_set_mag_mode(ak, val->val1);
			}
			else {
				LOG_WRN("Unexpected device attribute");
				rc = EINVAL;
			}
			break;
		case AK8963_CHAN_MAGN:
			if (attr == AK8963_ATTR_RESOLUTION) {
				rc = ak8963_set_mag_resolution(ak, val->val1);
			}
			break;	
		default: // Any other channel - unsupported
			LOG_WRN("Unsuported channel.");
			rc = EINVAL;
	}
	return rc;	
}

static const struct sensor_driver_api ak8963_driver_api = {
	.sample_fetch = ak8963_sample_fetch,
	.channel_get = ak8963_channel_get,
	.attr_set = ak8963_attr_set,
};

int ak8963_init(const struct device *ak)
{
	      struct ak8963_data *ak_data = ak->data;
	const struct ak8963_config *ak_cfg = ak->config;
	ak_data->i2c = device_get_binding(ak_cfg->i2c_label);
	if (ak_data->i2c == NULL) {
		LOG_ERR("Failed to get pointer to %s device!", ak_cfg->i2c_label);
		return -EINVAL;
	}
	int rc;
	// Wakeup chip
	rc = i2c_reg_update_byte(ak_data->i2c, ak_cfg->i2c_addr, 0x0A, BIT_MASK(4), BIT(1) | BIT(2));
	if (rc != 0) {
		LOG_ERR("Failed to wakeup AK8963.");
		return rc;
	} 
	// Check chip ID 
	uint8_t id;
	rc = i2c_reg_read_byte(ak_data->i2c, ak_cfg->i2c_addr, 0x00, &id); 
	if (rc != 0) {
		LOG_ERR("Failed to read chip ID.");
		return rc;
	}
	if (id != 0x48) {
		LOG_ERR("Invalid chip ID.");
		return -EINVAL;
	}
	// Wait for initialization of the registers
	k_sleep(K_MSEC(300));
	// Enter AK8963 fuse mode
	rc = i2c_reg_update_byte(ak_data->i2c, ak_cfg->i2c_addr, 0x0A, BIT_MASK(4), BIT_MASK(4));
	if (rc != 0) {
		LOG_ERR("Failed to enter AK8963 fuse mode.");
		return rc;
	} 
	// Write down AK8963 factory calibration data
	rc = i2c_burst_read(ak_data->i2c, ak_cfg->i2c_addr, 0x10, 
	                    (uint8_t *)&ak_data->raw_data.M_x_off, 6); 
	if (rc != 0) {
		LOG_ERR("Failed to read fuse register.");
		return rc;
	}
	// Sent chip to slip
	rc = i2c_reg_update_byte(ak_data->i2c, ak_cfg->i2c_addr, 0x0A, BIT_MASK(4), 0);
	if (rc != 0) {
		LOG_ERR("Failed to send AK8963 to sleep.");
		return rc;
	} 
	LOG_DBG("AK8963 is ready.");
	return 0;
}

static struct ak8963_data ak8963_data;
static const struct ak8963_config ak8963_config = {
	.i2c_label = DT_INST_BUS_LABEL(0),
	.i2c_addr = DT_INST_REG_ADDR(0),
};

DEVICE_DT_INST_DEFINE(0, ak8963_init, device_pm_control_nop, 
					  &ak8963_data, &ak8963_config, POST_KERNEL, 
					  CONFIG_SENSOR_INIT_PRIORITY, &ak8963_driver_api);

Kconfig

INI
Kconfig for AK8963
# AK8975 magnetometer configuration options
# This file describes options built-in AK8963; 
# For MPU9050 see its configuration. 
# Copyright (c) 2021 Anton Bondarenko
# SPDX-License-Identifier: Apache-2.0

config AK8963
	bool "AK8963 Magnetometer"
	depends on MPU9250
	depends on I2C
	help
	  Enable driver for AK8963 magnetometer.

CMakeLists.txt

INI
CMake list of files for AK8963
# SPDX-License-Identifier: Apache-2.0

zephyr_library()

zephyr_library_sources_ifdef(CONFIG_AK8963 ak8963.c)

CMakeLists.txt

INI
CMake file from ../ncs/zephyr/drivers/sensor folder with MPU9250 and AK8963 folders added
# SPDX-License-Identifier: Apache-2.0

add_subdirectory_ifdef(CONFIG_ADT7420		adt7420)
add_subdirectory_ifdef(CONFIG_ADXL345		adxl345)
add_subdirectory_ifdef(CONFIG_ADXL362		adxl362)
add_subdirectory_ifdef(CONFIG_ADXL372		adxl372)
add_subdirectory_ifdef(CONFIG_AK8975		ak8975)
add_subdirectory_ifdef(CONFIG_AK8963		ak8963)
add_subdirectory_ifdef(CONFIG_AMG88XX		amg88xx)
add_subdirectory_ifdef(CONFIG_AMS_IAQ_CORE	ams_iAQcore)
add_subdirectory_ifdef(CONFIG_APDS9960		apds9960)
add_subdirectory_ifdef(CONFIG_BMA280		bma280)
add_subdirectory_ifdef(CONFIG_BMC150_MAGN	bmc150_magn)
add_subdirectory_ifdef(CONFIG_BME280		bme280)
add_subdirectory_ifdef(CONFIG_BME680		bme680)
add_subdirectory_ifdef(CONFIG_BMG160		bmg160)
add_subdirectory_ifdef(CONFIG_BMI160		bmi160)
add_subdirectory_ifdef(CONFIG_BMM150		bmm150)
add_subdirectory_ifdef(CONFIG_BQ274XX		bq274xx)
add_subdirectory_ifdef(CONFIG_CCS811		ccs811)
add_subdirectory_ifdef(CONFIG_DHT			dht)
add_subdirectory_ifdef(CONFIG_DPS310		dps310)
add_subdirectory_ifdef(CONFIG_ENS210		ens210)
add_subdirectory_ifdef(CONFIG_FXAS21002		fxas21002)
add_subdirectory_ifdef(CONFIG_FXOS8700		fxos8700)
add_subdirectory(grove)
add_subdirectory_ifdef(CONFIG_TI_HDC		ti_hdc)
add_subdirectory_ifdef(CONFIG_HMC5883L		hmc5883l)
add_subdirectory_ifdef(CONFIG_HP206C		hp206c)
add_subdirectory_ifdef(CONFIG_HTS221		hts221)
add_subdirectory_ifdef(CONFIG_IIS2DH		iis2dh)
add_subdirectory_ifdef(CONFIG_IIS2DLPC		iis2dlpc)
add_subdirectory_ifdef(CONFIG_IIS2ICLX		iis2iclx)
add_subdirectory_ifdef(CONFIG_IIS2MDC		iis2mdc)
add_subdirectory_ifdef(CONFIG_IIS3DHHC		iis3dhhc)
add_subdirectory_ifdef(CONFIG_ISL29035		isl29035)
add_subdirectory_ifdef(CONFIG_ISM330DHCX	ism330dhcx)
add_subdirectory_ifdef(CONFIG_LIS2DH		lis2dh)
add_subdirectory_ifdef(CONFIG_LIS2DS12		lis2ds12)
add_subdirectory_ifdef(CONFIG_LIS2DW12          lis2dw12)
add_subdirectory_ifdef(CONFIG_LIS2MDL		lis2mdl)
add_subdirectory_ifdef(CONFIG_LIS3MDL		lis3mdl)
add_subdirectory_ifdef(CONFIG_LPS22HB		lps22hb)
add_subdirectory_ifdef(CONFIG_LPS22HH		lps22hh)
add_subdirectory_ifdef(CONFIG_LPS25HB		lps25hb)
add_subdirectory_ifdef(CONFIG_LSM303DLHC_MAGN	lsm303dlhc_magn)
add_subdirectory_ifdef(CONFIG_LSM6DS0		lsm6ds0)
add_subdirectory_ifdef(CONFIG_LSM6DSL		lsm6dsl)
add_subdirectory_ifdef(CONFIG_LSM6DSO		lsm6dso)
add_subdirectory_ifdef(CONFIG_LSM9DS0_GYRO	lsm9ds0_gyro)
add_subdirectory_ifdef(CONFIG_LSM9DS0_MFD	lsm9ds0_mfd)
add_subdirectory_ifdef(CONFIG_MAX17055		max17055)
add_subdirectory_ifdef(CONFIG_MAX30101		max30101)
add_subdirectory_ifdef(CONFIG_MAX44009		max44009)
add_subdirectory_ifdef(CONFIG_MCP9808		mcp9808)
add_subdirectory_ifdef(CONFIG_MPR			mpr)
add_subdirectory_ifdef(CONFIG_MPU6050		mpu6050)
add_subdirectory_ifdef(CONFIG_MPU9250		mpu9250)
add_subdirectory_ifdef(CONFIG_MS5607		ms5607)
add_subdirectory_ifdef(CONFIG_MS5837		ms5837)
add_subdirectory_ifdef(CONFIG_OPT3001		opt3001)
add_subdirectory_ifdef(CONFIG_PMS7003		pms7003)
add_subdirectory_ifdef(CONFIG_QDEC_NRFX		qdec_nrfx)
add_subdirectory_ifdef(CONFIG_TEMP_NRF5		nrf5)
add_subdirectory_ifdef(CONFIG_SHT3XD		sht3xd)
add_subdirectory_ifdef(CONFIG_SI7006		si7006)
add_subdirectory_ifdef(CONFIG_SI7055		si7055)
add_subdirectory_ifdef(CONFIG_SI7060		si7060)
add_subdirectory_ifdef(CONFIG_SM351LT		sm351lt)
add_subdirectory_ifdef(CONFIG_STTS751		stts751)
add_subdirectory_ifdef(CONFIG_SX9500		sx9500)
add_subdirectory_ifdef(CONFIG_TH02		    th02)
add_subdirectory_ifdef(CONFIG_TMP007		tmp007)
add_subdirectory_ifdef(CONFIG_TMP112		tmp112)
add_subdirectory_ifdef(CONFIG_TMP116		tmp116)
add_subdirectory_ifdef(CONFIG_VCNL4040		vcnl4040)
add_subdirectory_ifdef(CONFIG_VL53L0X		vl53l0x)
add_subdirectory_ifdef(CONFIG_TEMP_KINETIS	nxp_kinetis_temp)
add_subdirectory_ifdef(CONFIG_TACH_XEC		mchp_tach_xec)
add_subdirectory_ifdef(CONFIG_ITDS		wsen_itds)
add_subdirectory_ifdef(CONFIG_MCUX_ACMP		mcux_acmp)

zephyr_sources_ifdef(CONFIG_USERSPACE sensor_handlers.c)
zephyr_sources_ifdef(CONFIG_SENSOR_SHELL sensor_shell.c)
zephyr_sources_ifdef(CONFIG_SENSOR_SHELL_BATTERY shell_battery.c)

Madgwick filter for AHRS (Attitude and heading reference system)

C Header File
This is ported Kris Winer implementation of Sebastian Madgwick's filter.
/*
 * by: Kris Winer (c) 2014
 * Very small adaptation to remove globals 
 * and switch to arrays (hopefully)
 * by Anton Bondarenko 2021
 */

#define M_PI 3.14159

float sqrtf(float value);

void MadgwickQuaternionUpdate(float *q, float deltat, float beta, 
                               const float *a, const float *g, const float *m);

Madgwick filter for AHRS (Attitude and heading reference system)

C/C++
This is ported Kris Winer implementation of Sebastian Madgwick's filter.
/*
 * by: Kris Winer (c) 2014
 * Very small adaptation to remove globals 
 * and switch to arrays (hopefully)
 * by Anton Bondarenko 2021
 */

// Implementation of Sebastian Madgwick's "...efficient orientation filter for... inertial/magnetic sensor arrays"
// (see http://www.x-io.co.uk/category/open-source/ for examples and more details)
// which fuses acceleration, rotation rate, and magnetic moments to produce a quaternion-based estimate of absolute
// device orientation -- which can be converted to yaw, pitch, and roll. Useful for stabilizing quadcopters, etc.
// The performance of the orientation filter is at least as good as conventional Kalman-based filtering algorithms
// but is much less computationally intensive---it can be performed on a 3.3 V Pro Mini operating at 8 MHz!

#include "filter.h"

float sqrtf(float value){
	float sqrt = value / 3;
	if (value <= 0) {
		return 0;
	}
	for (int i = 0; i < 6; i++) {
		sqrt = (sqrt + value / sqrt) / 2;
	}
	return sqrt;
}

        void MadgwickQuaternionUpdate(float* q, float deltat, float beta, 
                                      const float *a, const float *g, const float *m){
            float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];   // short name local variable for readability
            float ax = a[0]*1E6f, ay = a[1]*1E6f, az = a[2]*1E6f;
            float gx = g[0]*M_PI/180.0f, gy = g[1]*M_PI/180.0f, gz = g[2]*M_PI/180.0f;
            float mx = m[0]*10.0f, my = m[1]*10.0f, mz = m[2]*10.0f;
            float norm;
            float hx, hy, _2bx, _2bz;
            float s1, s2, s3, s4;
            float qDot1, qDot2, qDot3, qDot4;

            // Auxiliary variables to avoid repeated arithmetic
            float _2q1mx;
            float _2q1my;
            float _2q1mz;
            float _2q2mx;
            float _4bx;
            float _4bz;
            float _2q1 = 2.0f * q1;
            float _2q2 = 2.0f * q2;
            float _2q3 = 2.0f * q3;
            float _2q4 = 2.0f * q4;
            float _2q1q3 = 2.0f * q1 * q3;
            float _2q3q4 = 2.0f * q3 * q4;
            float q1q1 = q1 * q1;
            float q1q2 = q1 * q2;
            float q1q3 = q1 * q3;
            float q1q4 = q1 * q4;
            float q2q2 = q2 * q2;
            float q2q3 = q2 * q3;
            float q2q4 = q2 * q4;
            float q3q3 = q3 * q3;
            float q3q4 = q3 * q4;
            float q4q4 = q4 * q4;

            // Normalise accelerometer measurement
            norm = sqrtf(ax * ax + ay * ay + az * az);
            if (norm == 0.0f) return; // handle NaN
            norm = 1.0f/norm;
            ax *= norm;
            ay *= norm;
            az *= norm;

            // Normalise magnetometer measurement
            norm = sqrtf(mx * mx + my * my + mz * mz);
            if (norm == 0.0f) return; // handle NaN
            norm = 1.0f/norm;
            mx *= norm;
            my *= norm;
            mz *= norm;

            // Reference direction of Earth's magnetic field
            _2q1mx = 2.0f * q1 * mx;
            _2q1my = 2.0f * q1 * my;
            _2q1mz = 2.0f * q1 * mz;
            _2q2mx = 2.0f * q2 * mx;
            hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4;
            hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4;
            _2bx = sqrtf(hx * hx + hy * hy);
            _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4;
            _4bx = 2.0f * _2bx;
            _4bz = 2.0f * _2bz;

            // Gradient decent algorithm corrective step
            s1 = -_2q3 * (2.0f * q2q4 - _2q1q3 - ax) + _2q2 * (2.0f * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
            s2 = _2q4 * (2.0f * q2q4 - _2q1q3 - ax) + _2q1 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q2 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + _2bz * q4 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
            s3 = -_2q1 * (2.0f * q2q4 - _2q1q3 - ax) + _2q4 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q3 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
            s4 = _2q2 * (2.0f * q2q4 - _2q1q3 - ax) + _2q3 * (2.0f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
            norm = sqrtf(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4);    // normalise step magnitude
            norm = 1.0f/norm;
            s1 *= norm;
            s2 *= norm;
            s3 *= norm;
            s4 *= norm;

            // Compute rate of change of quaternion
            qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1;
            qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2;
            qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3;
            qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4;

            // Integrate to yield quaternion
            q1 += qDot1 * deltat;
            q2 += qDot2 * deltat;
            q3 += qDot3 * deltat;
            q4 += qDot4 * deltat;
            norm = sqrtf(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);    // normalise quaternion
            norm = 1.0f/norm;
            q[0] = q1 * norm;
            q[1] = q2 * norm;
            q[2] = q3 * norm;
            q[3] = q4 * norm;

            return;
        }

main.c

C/C++
/*
 * Copyright (c) 2021 Anton Bondarenko
 * SPDX-License-Identifier: Apache-2.0
 * Based on demo for MPU9150
 * Copyright (c) 2019 Nordic Semiconductor ASA
 * SPDX-License-Identifier: Apache-2.0
 */

#include <kernel.h>
#include <sys_clock.h>
#include <errno.h>
#include <zephyr.h>
#include <device.h>
#include <drivers/i2c.h>
#include <drivers/sensor.h>
#include <drivers/sensor/mpu9250_ext.h>
#include <drivers/sensor/ak8963_ext.h>
#include <stdio.h>
#include <math.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(main);
#include "filter.h"
#define CONFIG_MPU9250_POLL_TIMEOUT 1 // move to config
#define CONFIG_MPU9250_CALIBRATION_TIMEOUT 10
#define CONFIG_AK8963_REFORM_FLUX false	// move to config
#define CONFIG_AK8963_CALIBRATE_SAMPLES 1500 // move to config

float ak8963_mag_bias[3] = {0.0, 0.0, 0.0};
float ak8963_mag_deform[3] = {1.0, 1.0, 1.0};
struct sensors {
	const struct device *mpu;
	const struct device *ak;
} sensors;

struct resolution {
	float A;
	float G;
	float T;
	float M;
} resolution;

static const char *now_str(void) {
	static char buf[16]; // ...HH:MM:SS.MMM 
	uint32_t now = k_uptime_get_32();
	uint32_t ms = now % MSEC_PER_SEC;
	uint32_t s, min, h;
	now /= MSEC_PER_SEC;
	s = now % 60U;
	now /= 60U;
	min = now % 60U;
	now /= 60U;
	h = now;
	snprintf(buf, sizeof(buf), "%u:%02u:%02u.%03u",
		 h, min, s, ms);
	return buf;
}

static inline float sensor_value_to_float(struct sensor_value *val) {
	return (float)val->val1 + (float)val->val2 / 1000000.0;
}

static inline float get_scaled_T(struct sensor_value *val) {
	return (sensor_value_to_float(val) - MPU9250_TEMP_ROOM_OFFSET) /
			MPU9250_TEMP_SENSITIVITY + MPU9250_TEMP_OFFSET;
}

static float get_scaled_A(struct sensor_value *val) {
	return sensor_value_to_float(val) * resolution.A;
}

static float get_scaled_G(struct sensor_value *val) {
	return sensor_value_to_float(val) * resolution.G;
}

static float get_scaled_M(struct sensor_value *val) {
	return sensor_value_to_float(val) * resolution.M;
}

static void mpu9250_print_data_frame(const struct device *mpu) {
	float buf[7];
	struct sensor_value val;
	int i = 0;
	sensor_channel_get(mpu, SENSOR_CHAN_DIE_TEMP, &val);
	buf[i++] = get_scaled_T(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_X, &val);
	buf[i++] = get_scaled_A(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_Y, &val);
	buf[i++] = get_scaled_A(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_Z, &val);
	buf[i++] = get_scaled_A(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_X, &val);
	buf[i++] = get_scaled_G(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_Y, &val);
	buf[i++] = get_scaled_G(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_Z, &val);
	buf[i++] = get_scaled_G(&val);
	printf("[%s]:%g Cel\n"
		   "  accel %f %f %f G\n"
		   "  gyro  %f %f %f deg/s\n",
		   now_str(), buf[0], 
		   buf[1], buf[2], buf[3], 
		   buf[4], buf[5], buf[6]);
}

static void mpu9250_print_raw_data_frame(const struct device *mpu) {
	int32_t buf[6];
	struct sensor_value val;
	int i = 0;
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_X, &val);
	buf[i++] = val.val1;
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_Y, &val);
	buf[i++] = val.val1;
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_Z, &val);
	buf[i++] = val.val1;
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_X, &val);
	buf[i++] = val.val1;
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_Y, &val);
	buf[i++] = val.val1;
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_Z, &val);
	buf[i++] = val.val1;
	printf(",accel,%d,%d,%d,,\n"
		   ",gyro,%d,%d,%d,,\n",
		   buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
}

static void ak8963_print_data_frame(const struct device *ak) {
	float buf[3];
	struct sensor_value val;
	int i = 0;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_X, &val);
	buf[i++] = (get_scaled_M(&val) - ak8963_mag_bias[0])/* * ak8963_mag_deform[0]*/;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_Y, &val);
	buf[i++] = (get_scaled_M(&val) - ak8963_mag_bias[1])/* * ak8963_mag_deform[1]*/;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_Z, &val);
    buf[i++] = (get_scaled_M(&val) - ak8963_mag_bias[2])/* * ak8963_mag_deform[2]*/;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_XYZ, &val);
	if(val.val1) {
		printf("  magn  %f %f %f mkT\n",buf[0], buf[1], buf[2]);
	}
	else{
		printf("  magn  %f %f %f mkT (INVALID?)\n",buf[0], buf[1], buf[2]);
	}
}

static float ak8963_get_M(const struct device *ak, enum sensor_channel chan) {
	float buf;
	struct sensor_value val;
	sensor_channel_get(ak, chan, &val);
	buf = (get_scaled_M(&val) - ak8963_mag_bias[0]);
	if(CONFIG_AK8963_REFORM_FLUX) {
		buf = buf * ak8963_mag_deform[chan - SENSOR_CHAN_MAGN_X]; 
	}
	return buf;
}

static void ak8963_print_raw_data_frame(const struct device *ak) {
	int32_t buf[3];
	struct sensor_value val;
	int i = 0;
	sensor_channel_get(ak, AK8963_CHAN_MAGN_RAW_X, &val);
	buf[i++] = val.val1;
	sensor_channel_get(ak, AK8963_CHAN_MAGN_RAW_Y, &val);
	buf[i++] = val.val1;
	sensor_channel_get(ak, AK8963_CHAN_MAGN_RAW_Z, &val);
    buf[i++] = val.val1;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_XYZ, &val);
	if(val.val1) {
		printf(",magn,%d,%d,%d,true\n", buf[0], buf[1], buf[2]);
	}
	else{
		printf(",magn,%d,%d,%d,true\n", buf[0], buf[1], buf[2]);
	}
}

static int process_mpu9250(const struct device *mpu) {
	int rc = sensor_sample_fetch(mpu);
	if( rc != 0){
		LOG_ERR("Failed to read raw data from MPU9250: %d", rc);
		return rc;
	}
	// do magic a lot
	// mpu9250_print_data_frame(mpu);
	// burst data to smartphone or to flash
	return rc;
}

static int process_ak8963(const struct device *ak) {
	int rc = sensor_sample_fetch(ak);
	if( rc == 0){
		// do magic a lot
		// ak8963_print_data_frame(ak);
		// burst data to smartphone or to flash

	}
	else {
		if(rc == EINVAL) {
			//LOG_DBG("Skipping damaged data frame.");
			//ak8963_print_data_frame(ak);
			rc = 0;
		}
		else {	
			LOG_ERR("Failed to read raw data from AK8963: %d", rc);
		}
	}
	return rc;
}

static void get_measured_data(const struct device *mpu, const struct device *ak, 
                              float *a, float *g, float *m) {
    struct sensor_value val;
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_X, &val);
	a[0] = get_scaled_A(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_Y, &val);
	a[1] = get_scaled_A(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_ACCEL_Z, &val);
	a[2] = get_scaled_A(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_X, &val);
	g[0] = get_scaled_G(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_Y, &val);
	g[1] = get_scaled_G(&val);
	sensor_channel_get(mpu, SENSOR_CHAN_GYRO_Z, &val);
	g[2] = get_scaled_G(&val); 
    sensor_channel_get(ak, SENSOR_CHAN_MAGN_X, &val);
	m[0] = (get_scaled_M(&val) - ak8963_mag_bias[0])/* * ak8963_mag_deform[0]*/;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_Y, &val);
	m[1] = (get_scaled_M(&val) - ak8963_mag_bias[1])/* * ak8963_mag_deform[1]*/;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_Z, &val);
    m[2] = (get_scaled_M(&val) - ak8963_mag_bias[2])/* * ak8963_mag_deform[2]*/;
	sensor_channel_get(ak, SENSOR_CHAN_MAGN_XYZ, &val);
    return;
}

static void log_data(bool ok, 
					 const float *a, const float *g, const float *m, const float *q, 
					 float yaw, float pitch, float roll){
	if(ok) {
		printf("\n,,");
	}
	else {
		printf("\n,Invalid?,");
	}
	printf("%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f",
	       a[0],a[1],a[2],g[0],g[1],g[2],m[0],m[1],m[2],q[0],q[1],q[2],q[3],yaw,pitch,roll);
}

static int calibrate_ak8963(const struct device *ak) {
	int rc = 0;
	int i, j;
	float buf;
	float bias_min[3];
	float bias_max[3];
	struct sensor_value val;
rep:k_sleep(K_MSEC(CONFIG_MPU9250_CALIBRATION_TIMEOUT));
	rc = sensor_sample_fetch(ak);
	if( rc == 0) {
		for(j = 0; j < 3; j++) {
			sensor_channel_get(ak, SENSOR_CHAN_MAGN_X + j, &val);
			buf = get_scaled_M(&val);
			bias_max[j] = buf;
			bias_min[j] = buf;
		}
	}
	else{
		if(rc == EINVAL){
			rc = 0;
			goto rep;
		}
		else {
			LOG_ERR("Calibration of AK8963 failed");
			return rc;
		}
	}
	for(i = 0; i < 3; i++) {
		ak8963_mag_bias[i] = 0.0;
		ak8963_mag_deform[i] = 1.0;
	}
	LOG_DBG("Calibration started.");
	for(i = 0; i < CONFIG_AK8963_CALIBRATE_SAMPLES; i++) {
		k_sleep(K_MSEC(CONFIG_MPU9250_CALIBRATION_TIMEOUT));
		rc = sensor_sample_fetch(ak);
		if( rc == 0) {
			for(j = 0; j < 3; j++) {
				sensor_channel_get(ak, SENSOR_CHAN_MAGN_X + j, &val);
				buf = get_scaled_M(&val);
				if(buf > bias_max[j]) {
					bias_max[j] = buf;
				}
				if(buf < bias_min[j]) {
					bias_min[j] = buf;
				}
			}
		}
		else{
			if(rc == EINVAL){
				rc = 0;
				continue;
			}
			else {
				return rc;
			}
		}
	}
	for(j = 0; j < 3; j++) {
		ak8963_mag_bias[j] = (bias_min[j] + bias_max[j]) / 2;
		ak8963_mag_deform[j] = (bias_max[j] - bias_min[j]) / 2;
	}
	buf = (ak8963_mag_deform[0] + ak8963_mag_deform[1] + ak8963_mag_deform[2]) / 3.0;
	for(j = 0; j < 3; j++) {
		ak8963_mag_deform[j] = buf / ak8963_mag_deform[j];
	}
	k_sleep(K_MSEC(100));
	LOG_DBG("Calibration completed.");
	return rc;
}

#ifdef CONFIG_MPU9250_TRIGGER
static struct sensor_trigger trigger;

static void handle_mpu9250_drdy(const struct device *dev,
				struct sensor_trigger *trig){
	int rc = process_mpu9250(sensors.mpu);
	if (rc != 0) {
		LOG_ERR("Cancelling trigger due to MPU9250 failure: %d\n", rc);
		(void)sensor_trigger_set(dev, trig, NULL);
	}
	int rc = process_ak8963(sensors.ak);
	if (rc != 0) {
		LOG_ERR("Cancelling trigger due to AK8963 failure: %d\n", rc);
		(void)sensor_trigger_set(dev, trig, NULL);
	}
}
#endif // CONFIG_MPU9250_TRIGGER 

void main(void) {
	int rc = 0;
	k_sleep(K_MSEC(400));
	// Setup MPU9250 device
	const char *const label0 = DT_LABEL(DT_INST(0, invensense_mpu9250));
	const struct device *mpu9250 = device_get_binding(label0);
	if (!mpu9250) {
		LOG_ERR("Failed to find sensor %s", label0);
		return;
	}
	sensors.mpu = mpu9250;
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_CONTROL, MPU9250_ATTR_CLOCK_SOURCE, 
				  &(struct sensor_value){ MPU9250_AUTO, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to set MPU9250 clock source.");
		return;
	}
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_CONTROL, MPU9250_ATTR_CLOCK_DIVIDER, 
				  &(struct sensor_value){ MPU9250_1000, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to set MPU9250 clock divider.");
		return;
	}
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_CONTROL, MPU9250_ATTR_WAKEUP, 
				  &(struct sensor_value){ 0, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to wakeup MPU9250.");
		return;
	}
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_CONTROL, MPU9250_ATTR_I2C_MODE,
				  &(struct sensor_value){ MPU9250_BYPASS, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to set I2C bypass mode for AK8963.");
		return;
	}
	//int mpu_gyro_range = MPU9250_G250DPS;
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_GYRO, MPU9250_ATTR_RANGE,
				  &(struct sensor_value){ MPU9250_G2000DPS, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to set gyrospope full-scale range.");
		return;
	}
	resolution.G = MPU9250_GYRO_RESOLUTION[MPU9250_G250DPS];
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_GYRO, MPU9250_ATTR_BANDWIDTH,
				  &(struct sensor_value){ MPU9250_GLPF_20HZ, MPU9250_FC11 });
	if(rc != 0) {
		LOG_ERR("Failed to set gyrospope bandwidth.");
		return;
	}
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_ACCEL, MPU9250_ATTR_RANGE,
				  &(struct sensor_value){ MPU9250_A2G, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to set accelerometer full-scale range.");
		return;
	}
	resolution.A = MPU9250_ACCEL_RESOLUTION[MPU9250_A2G];
	//int mpu_accel_range = MPU9250_A2G;
	rc = sensor_attr_set(mpu9250, MPU9250_CHAN_ACCEL, MPU9250_ATTR_BANDWIDTH,
				  &(struct sensor_value){ MPU9250_ALPF_20HZ, MPU9250_FC1 });
	if(rc != 0) {
		LOG_ERR("Failed to set accelerometer bandwidth.");
		return;
	}
	/* sensor_attr_set(mpu9250, MPU9250_CHAN_TEMP, MPU9250_ATTR_RESOLUTION,
			 &(struct sensor_value){ MPU9250_TEMP_RESOLUTION, MPU9250_TEMP_OFFSET }); */
			 
	// Setup AK8963 device
	const char * const label1 = DT_LABEL(DT_INST(0, asahi_kasei_ak8963));
	const struct device *ak8963 = device_get_binding(label1);
	if (!ak8963) {
		LOG_ERR("Failed to find sensor %s", label1);
		return;
	}
	sensors.ak = ak8963;
	rc = sensor_attr_set(ak8963, AK8963_CHAN_MAGN, AK8963_ATTR_RESOLUTION,
				  &(struct sensor_value){ AK8963_16B, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to set magnetometer resolution.");
		return;
	}
	resolution.M = AK8963_MAG_RESOLUTION[AK8963_16B];
	rc = sensor_attr_set(ak8963, AK8963_CHAN_CONTROL, AK8963_ATTR_MODE,
				  &(struct sensor_value){ AK8963_MODE_100HZ, 0 });
	if(rc != 0) {
		LOG_ERR("Failed to set magnetometer mode/bandwidth.");
		return;
	}
	// Setup NEO-6 device
	// LOG_DBG("Sensor drivers loaded and configured successfully");
	// Establish BLE to smartphone
	// LOG_DBG("Smartphone connected successfully");
#ifdef CONFIG_MPU9250_TRIGGER
	// Arm trigger for data ready signal of MPU6050
	trigger = (struct sensor_trigger) {
		.type = SENSOR_TRIG_DATA_READY,
		.chan = SENSOR_CHAN_ALL,
	};
	if (sensor_trigger_set(mpu9250, &trigger,
			       handle_mpu9250_drdy) < 0) {
		LOG_ERR("Failed to configure trigger.");
		//exit(EINVAL);
		//return;
	};
	LOG_DBG("Configured for triggered sampling.");
#endif
	LOG_DBG("SYSTEM IS UP");
	//k_sleep(K_MSEC(5000));
	//rc = calibrate_ak8963(ak8963);
	if(rc != 0) {
		LOG_ERR("AK8963 calibration failed: %d", rc);
		return;
	}

	// by Kris Wiener:
	// global constants for 9 DoF fusion and AHRS (Attitude and Heading Reference System)
	float GyroMeasError = M_PI * (40.0f / 180.0f);   // gyroscope measurement error in rads/s (start at 40 deg/s)
	float GyroMeasDrift = M_PI * (0.0f  / 180.0f);   // gyroscope measurement drift in rad/s/s (start at 0.0 deg/s/s)
	float beta = sqrtf(3.0f / 4.0f) * GyroMeasError;   // compute beta
//	float zeta = sqrtf(3.0f / 4.0f) * GyroMeasDrift;   // compute zeta, the other free parameter in the Madgwick scheme usually set to a small or zero value
//	#define Kp 2.0f * 5.0f // these are the free parameters in the Mahony filter and fusion scheme, Kp for proportional feedback, Ki for integral
//	#define Ki 0.0f
	float q[4] = {1.0f, 0.0f, 0.0f, 0.0f};    // vector to hold quaternion
	float deltat = 0.0f;
// float ax, ay, az, gx, gy, gz, mx, my, mz;
	float a[3];
	float g[3];
	float m[3];
	float pitch, yaw, roll;
	uint32_t lastUpdate = 0, now = 0;   // used to calculate integration interval
	
	//printf("\n\n\n\n[DATA OUTPUT START]");
	//printf("\n - Validity - Accelerometer (3) - Gyroscope (3) - Magnetometer (3) - Yaw - Pitch - Roll\n\n");
	
	LOG_DBG("MAIN LOOP START");
	for(int i = 0; i < 1000; i++) {
	//while (!IS_ENABLED(CONFIG_MPU9250_TRIGGER)) {
		rc = process_mpu9250(mpu9250);
		if (rc != 0) {
			LOG_DBG("ERROR READING MPU9250 DATA");
			break;
		}
		rc = process_ak8963(ak8963);
		if (rc != 0) {
			LOG_DBG("ERROR READING AK8963 DATA");
			break;
		}

		get_measured_data(mpu9250, ak8963, a, g, m);

		now = k_uptime_get_32();

  		deltat = ((now - lastUpdate)/1000.0f); // set integration time by time elapsed since last filter update
  		lastUpdate = now;

		MadgwickQuaternionUpdate(q, deltat, beta, a, g, m);

		// by: Kris Winer (c) 
		// Define output variables from updated quaternion---these are Tait-Bryan angles, commonly used in aircraft orientation.
		// In this coordinate system, the positive z-axis is down toward Earth. 
		// Yaw is the angle between Sensor x-axis and Earth magnetic North (or true North if corrected for local declination, looking down on the sensor positive yaw is counterclockwise.
		// Pitch is angle between sensor x-axis and Earth ground plane, toward the Earth is positive, up toward the sky is negative.
		// Roll is angle between sensor y-axis and Earth ground plane, y-axis up is positive roll.
		// These arise from the definition of the homogeneous rotation matrix constructed from quaternions.
		// Tait-Bryan angles as well as Euler angles are non-commutative; that is, the get the correct orientation the rotations must be
		// applied in the correct order which for this configuration is yaw, pitch, and then roll.
		// For more see http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles which has additional links.
    	yaw   = atan2(2.0f * (q[1] * q[2] + q[0] * q[3]), q[0] * q[0] + q[1] * q[1] - q[2] * q[2] - q[3] * q[3]);   
    	pitch = -asin(2.0f * (q[1] * q[3] - q[0] * q[2]));
    	roll  = atan2(2.0f * (q[0] * q[1] + q[2] * q[3]), q[0] * q[0] - q[1] * q[1] - q[2] * q[2] + q[3] * q[3]);
    	pitch *= 180.0f / M_PI;
    	yaw   *= 180.0f / M_PI; 
    	// yaw   -= 13.8; // Declination at Danville, California is 13 degrees 48 minutes and 47 seconds on 2014-04-04
		yaw -= 11.4; // Declination in Moscow, Russia is 11.382 on 2021-05-15.
    	roll  *= 180.0f / M_PI;
/*
		struct sensor_value val;
		sensor_channel_get(ak8963, SENSOR_CHAN_MAGN_XYZ, &val);
		bool ok = (val.val1 != 0);
		log_data(ok, a, g, m, q, yaw, pitch, roll);
*/		
		k_sleep(K_MSEC(CONFIG_MPU9250_POLL_TIMEOUT));
	}
	LOG_DBG("MAIN LOOP END");

	//printf("\n[DATA OUTPUT END]\n\n");
}

nrf5340dk_nrf5340_cpuappns.overlay

INI
Devicetree overlay for MPU9250 IMU and integrated AK8963 magnetometer.
/*
 * Copyright (c) 2019 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

&i2c1 {
	compatible = "nordic,nrf-twim";
	clock-frequency = <I2C_BITRATE_FAST>;
	status = "okay";
	mpu9250@68 {
		compatible = "invensense,mpu9250";
		reg = <0x68>;
		status = "okay";
		label = "MPU9250";
		int-gpios = <&gpio0 36 GPIO_ACTIVE_HIGH>; 
	};
	ak8963@0C {
		compatible = "asahi-kasei,ak8963";
		reg = <0x0C>;
		status = "okay";
		label = "AK8963"; 
	};
};

prj.conf

INI
System config
CONFIG_I2C=y
CONFIG_SENSOR=y
CONFIG_NRFX_TWIM=y
CONFIG_NRFX_TWIM1=y
CONFIG_I2C_NRFX=y
CONFIG_SENSOR_LOG_LEVEL_ERR=y
CONFIG_SENSOR_LOG_LEVEL_WRN=y
CONFIG_SENSOR_LOG_LEVEL_DBG=y
# CONFIG_SENSOR_LOG_LEVEL=2
# CONFIG_SENSOR_INIT_PRIORITY=90

CONFIG_MPU9250=y
CONFIG_AK8963=y
# CONFIG_MPU9250_TRIGGER=y

CONFIG_LOG=y

# ++
CONFIG_UART_CONSOLE=n
CONFIG_USE_SEGGER_RTT=n
CONFIG_RTT_CONSOLE=n
CONFIG_LOG_DEFAULT_LEVEL=4

# --
# CONFIG_LOG_BACKEND_UART=y

CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_CBPRINTF_LIBC_SUBSTS=y

CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_CMSIS_DSP_BASICMATH=y
CONFIG_CMSIS_DSP_COMPLEXMATH=y
CONFIG_CMSIS_DSP_FASTMATH=y

CMakeLists.txt for main program

Makefile
CMake config
#
# Copyright (c) 2019 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#

SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mfloat-abi=<soft/softfp/hard>")

cmake_minimum_required(VERSION 3.13.1)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(mpu9250)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

Credits

Anton Bondarenko

Anton Bondarenko

3 projects • 7 followers

Comments