STM32使用HAL库SPI驱动MPU9250九轴姿态传感器

前言

使用的开发软件为STM32CubeIDE,主控是STM32F103C8T6。前后用了两个GY-91模块才弄成,第一个GY-91模块挂羊头卖狗肉,读取WHO_AM_I寄存器的时候发现值是0x70,而MPU9250的WHO_AM_I寄存器的值是0x71或者0x73。查资料以后发现MPU6500的WHO_AM_I寄存器的值才是0x70,所以第一个模块也不可能读到磁力计的数据,因为MPU6500内部没有AK8963磁力计连接,为此我还困扰了好几天,换成新的总算是可以了。
希望大家在买模块或者芯片的时候擦亮眼睛,不要买到抹掉丝印的翻新货。

硬件配置

使用STM32CubeIDE进行配置如下所示
GPIO配置
在这里插入图片描述
使用SPI1和USART1,SPI1连接MPU9250,USART1用作DEBUG调试信息输出。另外,在使用STM32CubeMX配置的时候,一定要记得配置System Core/SYS里的Debug选项,使用JTAG就配置为JTAG,使用SWD就配置为Serial Wire,不然是不能下载和调试的。
SPI配置
在这里插入图片描述
这里我没有去测试SPI最高速度能到多少,有兴趣的朋友可以试试。

硬件连接

参照下表

MCU MPU9250
MISO SDA
MOSI SDO
SCK SCL
PB10(CS) nCS

nCS表示低电平选中

参考

https://github.com/desertkun/MPU9250
https://blog.csdn.net/liuyifanliu/article/details/99309839
以上两个方案第一个是用HAL库写的,第二个使用标准库写的,主要参考其中对寄存器的读写。其中第一个方案在读取AK8963时没有读取ST2寄存器,可能会导致读不出数据,也可能因为某些原因读不出陀螺仪的数据,第二个我没有做调试,这边就不再赘述这两个方案了。

文档

应当可以在网络上找到产品说明寄存器说明两个文档,如果找不到,我也已经在文章末尾放上了我自己的Github仓库链接也可以在里面找到这两个文档,主要开发过程就靠这两个文档支撑。

代码及说明

代码

代码如下,大部分说明会以注释的方式在代码中呈现
mpu9250.h

#ifndef __MPU9250_H
#define __MPU9250_H

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"
#include "spi.h"

#include <stdlib.h>

#define MPU9250_CS_GPIO    GY_CS_GPIO_Port
#define MPU9250_CS_PIN     GY_CS_Pin
#define MPU9250_SPI        hspi1
#define MPU9250_TIMEOUT_S  0x0100
#define MPU9250_TIMEOUT_L  0x1000

// MPU9250 registers

#define	SMPLRT_DIV		                    (uint8_t)0x19
#define	CONFIG			                    (uint8_t)0x1A
#define	GYRO_CONFIG		                    (uint8_t)0x1B
#define	ACCEL_CONFIG	                    (uint8_t)0x1C
#define	ACCEL_CONFIG_2                      (uint8_t)0x1D

#define INT_PIN_CFG                         (uint8_t)0x37
#define USER_CTRL                           (uint8_t)0x6a
#define I2C_MST_CTRL                        (uint8_t)0x24
#define I2C_MST_DELAY_CTRL                  (uint8_t)0x67
//--------------------i2c slv0-------------------------------//
#define I2C_SLV0_ADDR                       (uint8_t)0x25
#define I2C_SLV0_REG                        (uint8_t)0x26
#define I2C_SLV0_CTRL                       (uint8_t)0x27
#define I2C_SLV0_DO                         (uint8_t)0x63 //output reg
//--------------------AK8963 reg addr------------------------//
#define AK8963_I2C_ADDR                     (uint8_t)0x0C  //AKM addr
#define AK8963_WHOAMI_REG                   (uint8_t)0x00  //AKM ID addr
#define AK8963_WHOAMI_ID                    (uint8_t)0x48  //ID
#define AK8963_ST1_REG                      (uint8_t)0x02  //Data Status1
#define AK8963_ST2_REG                      (uint8_t)0x09  //Data reading end register & check Magnetic sensor overflow occurred
#define AK8963_ST1_DOR                      (uint8_t)0x02
#define AK8963_ST1_DRDY                     (uint8_t)0x01  //Data Ready
#define AK8963_ST2_BITM                     (uint8_t)0x10
#define AK8963_ST2_HOFL                     (uint8_t)0x08  // Magnetic sensor overflow
#define AK8963_CNTL1_REG                    (uint8_t)0x0A
#define AK8963_CNTL2_REG                    (uint8_t)0x0B
#define AK8963_CNTL2_SRST                   (uint8_t)0x01  //soft Reset
#define AK8963_ASAX                         (uint8_t)0x10  //X-axis sensitivity adjustment value
#define AK8963_ASAY                         (uint8_t)0x11  //Y-axis sensitivity adjustment value
#define AK8963_ASAZ                         (uint8_t)0x12  //Z-axis sensitivity adjustment value
//--------------------9axis  reg addr-----------------------//
#define	ACCEL_XOUT_H	(uint8_t)0x3B
#define	ACCEL_XOUT_L	(uint8_t)0x3C
#define	ACCEL_YOUT_H	(uint8_t)0x3D
#define	ACCEL_YOUT_L	(uint8_t)0x3E
#define	ACCEL_ZOUT_H	(uint8_t)0x3F
#define	ACCEL_ZOUT_L	(uint8_t)0x40

#define	TEMP_OUT_H		(uint8_t)0x41   //temperture
#define	TEMP_OUT_L		(uint8_t)0x42

#define	GYRO_XOUT_H		(uint8_t)0x43
#define	GYRO_XOUT_L		(uint8_t)0x44
#define	GYRO_YOUT_H		(uint8_t)0x45
#define	GYRO_YOUT_L		(uint8_t)0x46
#define	GYRO_ZOUT_H		(uint8_t)0x47
#define	GYRO_ZOUT_L		(uint8_t)0x48

#define MAG_XOUT_L		(uint8_t)0x03
#define MAG_XOUT_H		(uint8_t)0x04
#define MAG_YOUT_L		(uint8_t)0x05
#define MAG_YOUT_H		(uint8_t)0x06
#define MAG_ZOUT_L		(uint8_t)0x07
#define MAG_ZOUT_H		(uint8_t)0x08
//--------------------other reg addr-----------------------//
#define	PWR_MGMT_1		    (uint8_t)0x6B
#define PWR_MGMT_2          (uint8_t)0x6C
#define	WHO_AM_I		    (uint8_t)0x75

#define EXT_SENS_DATA_00    (uint8_t)0x49
#define EXT_SENS_DATA_01    (uint8_t)0x4a
#define EXT_SENS_DATA_02    (uint8_t)0x4b
#define EXT_SENS_DATA_03    (uint8_t)0x4c

typedef enum __MPU9250_AccelRange {
	MPU9250_Accel_Range_2G = 0x00,
	MPU9250_Accel_Range_4G = 0x08,
	MPU9250_Accel_Range_8G = 0x10,
	MPU9250_Accel_Range_16G = 0x18
/*
 * 0b00000000 for +-2G
 * 0b00001000 for +-4G
 * 0b00010000 for +-8G
 * 0b00011000 for +-16G
 * */
} MPU9250_AccelRange;

typedef enum __MPU9250_GyroRange {
	MPU9250_Gyro_Range_250dps = 0x00,
	MPU9250_Gyro_Range_500dps = 0x08,
	MPU9250_Gyro_Range_1000dps = 0x10,
	MPU9250_Gyro_Range_2000dps = 0x18
/*
 * 0b00000000 for 250dps
 * 0b00001000 for 500dps
 * 0b00010000 for 1000dps
 * 0b00011000 for 2000dps
 * */
} MPU9250_GyroRange;

typedef enum __MPU9250_Accel_DLPFBandwidth {
	// set reg29 Bit[3] & Bit[2:0]
	// Hz
	MPU9250_Accel_DLPFBandwidth_460 = 0x00,    // delay 1.94ms
	MPU9250_Accel_DLPFBandwidth_184,           // delay 5.80ms
	MPU9250_Accel_DLPFBandwidth_92,            // delay 7.80ms
	MPU9250_Accel_DLPFBandwidth_41,            // delay 11.80ms
	MPU9250_Accel_DLPFBandwidth_20,            // delay 19.80ms
	MPU9250_Accel_DLPFBandwidth_10,            // delay 35.70ms
	MPU9250_Accel_DLPFBandwidth_5,             // delay 66.96ms
	MPU9250_Accel_DLPFBandwidth_460_2,         // delay 1.94ms
	MPU9250_Accel_DLPFBandwidth_1_13k = 0x08,  // delay 0.75ms

} MPU9250_Accel_DLPFBandwidth;

typedef enum __MPU9250_Accel_SampleRateDivider {
	// check reg30, when use lower power mode
	// Hz
	// Bandwidth 1.1kHz, Delay 1ms
	MPU9250_LP_ACCEL_ODR_0_24HZ = 0x00,
	MPU9250_LP_ACCEL_ODR_0_49HZ,
	MPU9250_LP_ACCEL_ODR_0_98HZ,
	MPU9250_LP_ACCEL_ODR_1_95HZ,
	MPU9250_LP_ACCEL_ODR_3_91HZ,
	MPU9250_LP_ACCEL_ODR_7_81HZ,
	MPU9250_LP_ACCEL_ODR_15_63HZ,
	MPU9250_LP_ACCEL_ODR_31_25HZ,
	MPU9250_LP_ACCEL_ODR_62_50HZ,
	MPU9250_LP_ACCEL_ODR_125HZ,
	MPU9250_LP_ACCEL_ODR_250HZ,
	MPU9250_LP_ACCEL_ODR_500HZ
} MPU9250_Accel_SampleRateDivider;

typedef enum __MPU9250_Gyro_DLPFBandwidth {
	// to use following 2 options, reg27 Bit[1:0](fchoice_b) must be set
	// these to options are not related to reg26 Bit[2:0](DLPF_CFG), but reg27 Bit[1:0](fchoice_b)
	// notice: this options can also affect temperature sensor
	// Hz
	MPU9250_Gyro_DLPFBandwidth_8800_x = 0x00,    // delay 0.064ms
	MPU9250_Gyro_DLPFBandwidth_3600_x = 0x00,    // delay 0.11ms

	// follow options can be set to reg26 Bit[2:0](DLPF_CFG) to set DLPFBandwidth
	MPU9250_Gyro_DLPFBandwidth_250 = 0x00,     // delay 0.97ms
	MPU9250_Gyro_DLPFBandwidth_184,            // delay 2.9ms
	MPU9250_Gyro_DLPFBandwidth_92,             // delay 3.9ms
	MPU9250_Gyro_DLPFBandwidth_41,             // delay 5.9ms
	MPU9250_Gyro_DLPFBandwidth_20,             // delay 9.9ms
	MPU9250_Gyro_DLPFBandwidth_10,             // delay 17.85ms
	MPU9250_Gyro_DLPFBandwidth_5,              // delay 33.48ms
	MPU9250_Gyro_DLPFBandwidth_3600,           // delay 0.17ms

} MPU9250_Gyro_DLPFBandwidth;

typedef struct __MPU9250_PropTypDef {

// reserved, maybe useless

} MPU9250_PropTypeDef;

typedef struct __MPU9250_DataTypeDef {
	// origin data from mpu9250
	volatile int16_t Accel_row[3];
	volatile int16_t Gyro_row[3];
	volatile int16_t Magn_row[3];
	// store real data in float
	float Accel[3];
	float Gyro[3];
	float Magn[3];

} MPU9250_DataTypeDef;

typedef struct __MPU9250 {
	MPU9250_PropTypeDef mpu_prop;
	MPU9250_DataTypeDef mpu_data;
} MPU9250;

uint8_t mpu_r_ak8963_WhoAmI(MPU9250 *mpu);
uint8_t mpu_r_WhoAmI(MPU9250 *mpu);
uint8_t MPU9250_Init(MPU9250 *mpu);
void MPU9250_ReadAccel(MPU9250 *mpu);
void MPU9250_ReadGyro(MPU9250 *mpu);
void MPU9250_ReadMag(MPU9250 *mpu);

#ifdef __cplusplus
}
#endif

#endif

mpu9250.c

/*
 ************ https://github.com/sin1111yi ************
 ******************************************************
 *        .__       ____ ____ ____ ____        .__
 *   _____|__| ____/_   /_   /_   /_   |___.__.|__|
 *  /  ___/  |/    \|   ||   ||   ||   <   |  ||  |
 *  \___ \|  |   |  \   ||   ||   ||   |\___  ||  |
 *  /___  >__|___|  /___||___||___||___|/ ____||__|
 *      \/        \/                    \/
 ******************************************************
 * @beginning
 * I don't like over encapsulation, low freedom and less api.
 * After read several projects on Github, I decided to write it by myself.
 *
 * @suggestions
 * If someone want to use SPI to drive MPU9250 to read 9DOF data,
 * I'll suggest him/her to use MPU6500 and another independent magnetometer chip.
 * Because MPU9250 is MPU6500 & AK8963, and AK8963 doesn't support SPI serial.
 * AK8963 must drive by I2C serial, which is put in MPU9250 connect MPU6500 and AK8963.
 * That means to drive AK8963, I need use SPI to drive MPU6500's I2C to drive AK8963.
 * That's troublesome. But it can be much more easier when using I2C.
 *
 * @my own view
 * I think it is stupid to use MPU9250, if PCB space and capital are enough.
 * And I think the designers of MPU9250 are really lazy, cause they name their registers in many different ways.
 *
 * @other
 * Those words above only represent myself. If someone have different views, please ignore my views.
 *
 *
 * @author  sin1111yi
 * @date    2021/12/9   restructure version 1
 *          2021/12/10  restructure version 2
 *
 * @refer   https://github.com/desertkun/MPU9250
 *          https://blog.csdn.net/liuyifanliu/article/details/99309839
 *
 */

#include "mpu9250.h"

#define mpu_select()    HAL_GPIO_WritePin(MPU9250_CS_GPIO, MPU9250_CS_PIN, GPIO_PIN_RESET)
#define mpu_deselect()  HAL_GPIO_WritePin(MPU9250_CS_GPIO, MPU9250_CS_PIN, GPIO_PIN_SET)

#define DATABUF_SIZ     16

static uint8_t dataBuf[DATABUF_SIZ] = { 0 }; // all read data will store in this array
static uint16_t i = 0; // loop control

/*** basic spi operate ***/
/*** I want to make these functions can be reused ***/
/*
 * @brief   write a byte through SPI and read feedback
 * @param   byte: byte to write
 * @return  received byte
 * */
static uint8_t spi_wr_byte(SPI_HandleTypeDef *hspi, uint8_t byte) {
	uint8_t feedback = 0;

	// wait SPI serial free
	while (HAL_SPI_GetState(hspi) == HAL_SPI_STATE_BUSY_TX_RX)
		;

	if (HAL_SPI_TransmitReceive(hspi, &byte, &feedback, 1, 0x01f4) != HAL_OK) {
		return 0xff;
	}

	return feedback;
}
/*
 * @brief   write several bytes though spi
 * @param   address: address of the first reg
 * @param   bytes: number of bytes to write
 * @param   num: number of bytes
 * */
static void spi_w_bytes(SPI_HandleTypeDef *hspi, uint8_t address,
		uint8_t *bytes, uint16_t num) {
	mpu_select();

	spi_wr_byte(hspi, address);
	for (i = 0; i < num; i++)
		spi_wr_byte(hspi, bytes[i]);

	mpu_deselect();
}

/*
 * @brief   read several bytes though spi
 * @param   address: address of the first reg
 * @param   num: number of bytes to read, number < DATABUF_SIZ
 * @return  data read array
 * */
static void spi_r_bytes(SPI_HandleTypeDef *hspi, uint8_t address, uint8_t num) {
	uint8_t _address = address | 0x80;

	mpu_select();

	// may be can use HAL_SPI_TransmitReceive()
	HAL_SPI_Transmit(hspi, &_address, 1, 0x01f4);
	HAL_SPI_Receive(hspi, dataBuf, num, 0x01f4); // store read data to dataBuf

	mpu_deselect();

}
/*** basic mpu9250 operate ***/
/*
 * @brief   write mpu9250 reg though spi
 * @param   address: address of reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_reg(uint8_t address, uint8_t byte) {
	spi_w_bytes(&MPU9250_SPI, address, &byte, 1);
}

/*
 * @brief   read mpu9250 regs though spi
 * @param   address: address of reg to write
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_regs(uint8_t address, uint8_t num) {
	spi_r_bytes(&MPU9250_SPI, address, num);
}
/******* basic ak8963 operate *******/
/*
 * @brief   write AK8963 regs though I2C in MPU9250
 * @param   address: address of AK8963 reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_ak8963_reg(uint8_t address, uint8_t byte) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_DO, byte);
	mpu_w_reg(I2C_SLV0_CTRL, 0x81);
}
/*
 * @brief read AK8963 regs though I2C in MPU9250
 * @param   address: first address of AK8963 regs to read
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_ak8963_regs(uint8_t address, uint8_t num) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR | 0x80);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_CTRL, 0x80 | num);
	HAL_Delay(1);
	mpu_r_regs(EXT_SENS_DATA_00, num);
}
/*
 * @brief   read ak8963 WHO_AM_I reg
 * @return  AK8963 WHO_AM_I value, expected to be 0x48
 * */
uint8_t mpu_r_ak8963_WhoAmI(MPU9250 *mpu) {
	mpu_r_ak8963_regs(AK8963_WHOAMI_REG, 1);
	return dataBuf[0];
}
/*
 * @brief   read mpu9250(mpu6500) WHO_AM_I reg
 * @return  mpu9250(mpu6500) WHO_AM_I value, expected to be 0x48
 * */
uint8_t mpu_r_WhoAmI(MPU9250 *mpu) {
	mpu_r_regs(WHO_AM_I, 1);
	return dataBuf[0];
}
/*
 * @brief   init origin data
 * */
static void MPU9250_StructInit(MPU9250 *mpu) {

	for (uint8_t i = 0; i < 3; i++) {
		mpu->mpu_data.Accel[i] = 0;
		mpu->mpu_data.Gyro[i] = 0;
		mpu->mpu_data.Magn[i] = 0;

		mpu->mpu_data.Accel_row[i] = 0;
		mpu->mpu_data.Gyro_row[i] = 0;
		mpu->mpu_data.Magn_row[i] = 0.0;
	}
}
/*
 * @brief   init mpu9250
 * */
uint8_t MPU9250_Init(MPU9250 *mpu) {
	MPU9250_StructInit(mpu);

	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x80); // reset MPU9250, reg107
	HAL_Delay(10);
	mpu_w_reg(USER_CTRL, (uint8_t) 0x20); // enable I2C master mode, reg106
	mpu_w_reg(I2C_MST_CTRL, (uint8_t) 0x0D); // set I2C clock speed to 400kHz, reg36
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x00); // set AK8963 to power down
	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x80); // reset MPU9250, Bit[7] will auto clear
	HAL_Delay(10);
	mpu_w_ak8963_reg(AK8963_CNTL2_REG, AK8963_CNTL2_SRST); // reset AK8963
	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x01); // select clock source
	mpu_w_reg(PWR_MGMT_2, (uint8_t) 0x00); // enable accel and gyro

	/* init GYRO and ACCEL */
	mpu_w_reg(SMPLRT_DIV, (uint8_t) 0x00); // SAMPLE_RATE= Internal_Sample_Rate / (1 + SMPLRT_DIV), Internal_Sample_Rate==8K
	mpu_w_reg(GYRO_CONFIG, (uint8_t) MPU9250_Gyro_Range_2000dps); // gyro full scale select
	mpu_w_reg(ACCEL_CONFIG, (uint8_t) MPU9250_Accel_Range_16G); // accel full scale select
	mpu_w_reg(ACCEL_CONFIG_2, (uint8_t) MPU9250_Accel_DLPFBandwidth_460);
	mpu_w_reg(CONFIG, (uint8_t) MPU9250_Gyro_DLPFBandwidth_250);
	/* init MAG */
	mpu_w_reg(USER_CTRL, (uint8_t) 0x20); // enable I2C master mode
	mpu_w_reg(I2C_MST_CTRL, (uint8_t) 0x0D); // set I2C clock speed to 400kHz, reg36
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x00); // set AK8963 to power down
	HAL_Delay(100);
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x0f); // set AK8963 to Fuse ROM access mode
	HAL_Delay(100);
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x00); // set AK8963 to power down
	HAL_Delay(100);
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x16); // AK8963 working on Continuous measurement mode 2 & 16-bit output
	HAL_Delay(100);
	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x01); // select clock source
	mpu_r_ak8963_regs(MAG_XOUT_L, 7);

	return 0x00;
}
/*
 * @brief   read accel origin value and calculate real value
 *          data will be stored in mpu
 * */
void MPU9250_ReadAccel(MPU9250 *mpu) {
	// m/s
	mpu_r_regs(ACCEL_XOUT_H, 6);
	// calculate x axis
	mpu->mpu_data.Accel_row[0] = (dataBuf[0] << 8) | dataBuf[1];
	mpu->mpu_data.Accel[0] = (float) mpu->mpu_data.Accel_row[0] / 208.980;

	// calculate y axis
	mpu->mpu_data.Accel_row[1] = (dataBuf[2] << 8) | dataBuf[3];
	mpu->mpu_data.Accel[1] = (float) mpu->mpu_data.Accel_row[1] / 208.980;

	// calculate z axis
	mpu->mpu_data.Accel_row[2] = (dataBuf[4] << 8) | dataBuf[5];
	mpu->mpu_data.Accel[2] = (float) mpu->mpu_data.Accel_row[2] / 208.980;
}
/*
 * @brief   read gyro origin value and calculate real value
 *          data will be stored in mpu
 * */
void MPU9250_ReadGyro(MPU9250 *mpu) {
	// d/s
	mpu_r_regs(GYRO_XOUT_H, 6);
	// calculate x axis
	mpu->mpu_data.Gyro_row[0] = (dataBuf[0] << 8) | dataBuf[1];
	mpu->mpu_data.Gyro[0] = mpu->mpu_data.Gyro_row[0] / 16.384;

	// calculate y axis
	mpu->mpu_data.Gyro_row[1] = (dataBuf[2] << 8) | dataBuf[3];
	mpu->mpu_data.Gyro[1] = mpu->mpu_data.Gyro_row[1] / 16.384;

	// calculate z axis
	mpu->mpu_data.Gyro_row[2] = (dataBuf[4] << 8) | dataBuf[5];
	mpu->mpu_data.Gyro[2] = mpu->mpu_data.Gyro_row[2] / 16.384;
}
/*
 * @brief   read mag origin value and calculate real value
 *          data will be stored in mpu
 *
 * @notice  this part may have some bugs, but I guess my GY-91 is broken
 *          until I get my new module, I can't judge where the problem is
 * */
void MPU9250_ReadMag(MPU9250 *mpu) {
	uint8_t mag_adjust[3] = { 0 };
	uint8_t mag_buffer[6] = { 0 };

	mpu_r_ak8963_regs(AK8963_ASAX, 3);
	mag_adjust[0] = dataBuf[0];
	mag_adjust[1] = dataBuf[1];
	mag_adjust[2] = dataBuf[2];

	// read AK8963_ST2_REG is necessary
	// ST2 register has a role as data reading end register(on page 51)

	mpu_r_ak8963_regs(MAG_XOUT_L, 1);
	mag_buffer[0] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1); // data read finish reg
	mpu_r_ak8963_regs(MAG_XOUT_H, 1);
	mag_buffer[1] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);

	mpu_r_ak8963_regs(MAG_YOUT_L, 1);
	mag_buffer[2] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);
	mpu_r_ak8963_regs(MAG_YOUT_H, 1);
	mag_buffer[3] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);

	mpu_r_ak8963_regs(MAG_ZOUT_L, 1);
	mag_buffer[4] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);
	mpu_r_ak8963_regs(MAG_ZOUT_H, 1);
	mag_buffer[5] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);


	mpu->mpu_data.Magn_row[0] = (mag_buffer[1] << 8) | mag_buffer[0];
	mpu->mpu_data.Magn_row[1] = (mag_buffer[3] << 8) | mag_buffer[2];
	mpu->mpu_data.Magn_row[2] = (mag_buffer[5] << 8) | mag_buffer[4];

	// calculate real value, check page53
	mpu->mpu_data.Magn[0] = (float) mpu->mpu_data.Magn_row[0]
			* (((mag_adjust[0] - 128) / 256.0) + 1);
	mpu->mpu_data.Magn[0] = mpu->mpu_data.Magn_row[0] * 0.15;

	mpu->mpu_data.Magn[1] = (float) mpu->mpu_data.Magn_row[1]
			* (((mag_adjust[1] - 128) / 256.0) + 1);
	mpu->mpu_data.Magn[1] = mpu->mpu_data.Magn_row[1] * 0.15;

	mpu->mpu_data.Magn[2] = (float) mpu->mpu_data.Magn_row[2]
			* (((mag_adjust[2] - 128) / 256.0) + 1);
	mpu->mpu_data.Magn[2] = mpu->mpu_data.Magn_row[2] * 0.15;
}

详细说明

基础SPI驱动

包括读写、读、写

/*** basic spi operate ***/
/*** I want to make these functions can be reused ***/
/*
 * @brief   write a byte through SPI and read feedback
 * @param   byte: byte to write
 * @return  received byte
 * */
static uint8_t spi_wr_byte(SPI_HandleTypeDef *hspi, uint8_t byte) {
	uint8_t feedback = 0;

	// wait SPI serial free
	while (HAL_SPI_GetState(hspi) == HAL_SPI_STATE_BUSY_TX_RX)
		;

	if (HAL_SPI_TransmitReceive(hspi, &byte, &feedback, 1, 0x01f4) != HAL_OK) {
		return 0xff;
	}

	return feedback;
}
/*
 * @brief   write several bytes though spi
 * @param   address: address of the first reg
 * @param   bytes: number of bytes to write
 * @param   num: number of bytes
 * */
static void spi_w_bytes(SPI_HandleTypeDef *hspi, uint8_t address,
		uint8_t *bytes, uint16_t num) {
	mpu_select();

	spi_wr_byte(hspi, address);
	for (i = 0; i < num; i++)
		spi_wr_byte(hspi, bytes[i]);

	mpu_deselect();
}

/*
 * @brief   read several bytes though spi
 * @param   address: address of the first reg
 * @param   num: number of bytes to read, number < DATABUF_SIZ
 * @return  data read array
 * */
static void spi_r_bytes(SPI_HandleTypeDef *hspi, uint8_t address, uint8_t num) {
	uint8_t _address = address | 0x80;

	mpu_select();

	// may be can use HAL_SPI_TransmitReceive()
	HAL_SPI_Transmit(hspi, &_address, 1, 0x01f4);
	HAL_SPI_Receive(hspi, dataBuf, num, 0x01f4); // store read data to dataBuf

	mpu_deselect();

}

基础MPU驱动

将基础SPI驱动部分代码封装在其中

/*** basic mpu9250 operate ***/
/*
 * @brief   write mpu9250 reg though spi
 * @param   address: address of reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_reg(uint8_t address, uint8_t byte) {
	spi_w_bytes(&MPU9250_SPI, address, &byte, 1);
}

/*
 * @brief   read mpu9250 regs though spi
 * @param   address: address of reg to write
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_regs(uint8_t address, uint8_t num) {
	spi_r_bytes(&MPU9250_SPI, address, num);
}

基础AK8963驱动

/******* basic ak8963 operate *******/
/*
 * @brief   write AK8963 regs though I2C in MPU9250
 * @param   address: address of AK8963 reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_ak8963_reg(uint8_t address, uint8_t byte) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_DO, byte);
	mpu_w_reg(I2C_SLV0_CTRL, 0x81);
}
/*
 * @brief read AK8963 regs though I2C in MPU9250
 * @param   address: first address of AK8963 regs to read
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_ak8963_regs(uint8_t address, uint8_t num) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR | 0x80);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_CTRL, 0x80 | num);
	HAL_Delay(1);
	mpu_r_regs(EXT_SENS_DATA_00, num);
}

寄存器说明

I2C_SLV0_DO寄存器
在这里插入图片描述
意思是如果想向I2C从设备0写入数据,只要写入这个寄存器即可,可以理解为向这个寄存器写数据即为向从设备0写数据。

I2C_SLV0_CTRL寄存器
在这里插入图片描述
在这里插入图片描述
根据上表就可知道如果需要从I2C从设备中读取数据就要需要给该寄存器第[7]位置1,读取的数据个数为第[3:0]位的值,如果需要从I2C从设备中读取1个字节,则要向该寄存器写入0b10000001,即十六进制的0x81,读取3个字节,则要向该寄存器写入0b10000011,即十六进制的0x83。
其他寄存器请查阅上面的代码注释以及官方的寄存器手册。

总结

买模块之前想清楚,拿到模块之后看清楚
多查文档,芯片文档才是开发的最好助力
多看别人的代码,别人的代码总能给到帮助
多运行,多调试,别人的代码不一定最好

Github

https://github.com/sin1111yi/stm32_mpu9250_spi_hal_lib

版权声明:本文为CSDN博主「sin1111yi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sin1111yi/article/details/121906053

前言

使用的开发软件为STM32CubeIDE,主控是STM32F103C8T6。前后用了两个GY-91模块才弄成,第一个GY-91模块挂羊头卖狗肉,读取WHO_AM_I寄存器的时候发现值是0x70,而MPU9250的WHO_AM_I寄存器的值是0x71或者0x73。查资料以后发现MPU6500的WHO_AM_I寄存器的值才是0x70,所以第一个模块也不可能读到磁力计的数据,因为MPU6500内部没有AK8963磁力计连接,为此我还困扰了好几天,换成新的总算是可以了。
希望大家在买模块或者芯片的时候擦亮眼睛,不要买到抹掉丝印的翻新货。

硬件配置

使用STM32CubeIDE进行配置如下所示
GPIO配置
在这里插入图片描述
使用SPI1和USART1,SPI1连接MPU9250,USART1用作DEBUG调试信息输出。另外,在使用STM32CubeMX配置的时候,一定要记得配置System Core/SYS里的Debug选项,使用JTAG就配置为JTAG,使用SWD就配置为Serial Wire,不然是不能下载和调试的。
SPI配置
在这里插入图片描述
这里我没有去测试SPI最高速度能到多少,有兴趣的朋友可以试试。

硬件连接

参照下表

MCU MPU9250
MISO SDA
MOSI SDO
SCK SCL
PB10(CS) nCS

nCS表示低电平选中

参考

https://github.com/desertkun/MPU9250
https://blog.csdn.net/liuyifanliu/article/details/99309839
以上两个方案第一个是用HAL库写的,第二个使用标准库写的,主要参考其中对寄存器的读写。其中第一个方案在读取AK8963时没有读取ST2寄存器,可能会导致读不出数据,也可能因为某些原因读不出陀螺仪的数据,第二个我没有做调试,这边就不再赘述这两个方案了。

文档

应当可以在网络上找到产品说明寄存器说明两个文档,如果找不到,我也已经在文章末尾放上了我自己的Github仓库链接也可以在里面找到这两个文档,主要开发过程就靠这两个文档支撑。

代码及说明

代码

代码如下,大部分说明会以注释的方式在代码中呈现
mpu9250.h

#ifndef __MPU9250_H
#define __MPU9250_H

#ifdef __cplusplus
extern "C" {
#endif

#include "main.h"
#include "spi.h"

#include <stdlib.h>

#define MPU9250_CS_GPIO    GY_CS_GPIO_Port
#define MPU9250_CS_PIN     GY_CS_Pin
#define MPU9250_SPI        hspi1
#define MPU9250_TIMEOUT_S  0x0100
#define MPU9250_TIMEOUT_L  0x1000

// MPU9250 registers

#define	SMPLRT_DIV		                    (uint8_t)0x19
#define	CONFIG			                    (uint8_t)0x1A
#define	GYRO_CONFIG		                    (uint8_t)0x1B
#define	ACCEL_CONFIG	                    (uint8_t)0x1C
#define	ACCEL_CONFIG_2                      (uint8_t)0x1D

#define INT_PIN_CFG                         (uint8_t)0x37
#define USER_CTRL                           (uint8_t)0x6a
#define I2C_MST_CTRL                        (uint8_t)0x24
#define I2C_MST_DELAY_CTRL                  (uint8_t)0x67
//--------------------i2c slv0-------------------------------//
#define I2C_SLV0_ADDR                       (uint8_t)0x25
#define I2C_SLV0_REG                        (uint8_t)0x26
#define I2C_SLV0_CTRL                       (uint8_t)0x27
#define I2C_SLV0_DO                         (uint8_t)0x63 //output reg
//--------------------AK8963 reg addr------------------------//
#define AK8963_I2C_ADDR                     (uint8_t)0x0C  //AKM addr
#define AK8963_WHOAMI_REG                   (uint8_t)0x00  //AKM ID addr
#define AK8963_WHOAMI_ID                    (uint8_t)0x48  //ID
#define AK8963_ST1_REG                      (uint8_t)0x02  //Data Status1
#define AK8963_ST2_REG                      (uint8_t)0x09  //Data reading end register & check Magnetic sensor overflow occurred
#define AK8963_ST1_DOR                      (uint8_t)0x02
#define AK8963_ST1_DRDY                     (uint8_t)0x01  //Data Ready
#define AK8963_ST2_BITM                     (uint8_t)0x10
#define AK8963_ST2_HOFL                     (uint8_t)0x08  // Magnetic sensor overflow
#define AK8963_CNTL1_REG                    (uint8_t)0x0A
#define AK8963_CNTL2_REG                    (uint8_t)0x0B
#define AK8963_CNTL2_SRST                   (uint8_t)0x01  //soft Reset
#define AK8963_ASAX                         (uint8_t)0x10  //X-axis sensitivity adjustment value
#define AK8963_ASAY                         (uint8_t)0x11  //Y-axis sensitivity adjustment value
#define AK8963_ASAZ                         (uint8_t)0x12  //Z-axis sensitivity adjustment value
//--------------------9axis  reg addr-----------------------//
#define	ACCEL_XOUT_H	(uint8_t)0x3B
#define	ACCEL_XOUT_L	(uint8_t)0x3C
#define	ACCEL_YOUT_H	(uint8_t)0x3D
#define	ACCEL_YOUT_L	(uint8_t)0x3E
#define	ACCEL_ZOUT_H	(uint8_t)0x3F
#define	ACCEL_ZOUT_L	(uint8_t)0x40

#define	TEMP_OUT_H		(uint8_t)0x41   //temperture
#define	TEMP_OUT_L		(uint8_t)0x42

#define	GYRO_XOUT_H		(uint8_t)0x43
#define	GYRO_XOUT_L		(uint8_t)0x44
#define	GYRO_YOUT_H		(uint8_t)0x45
#define	GYRO_YOUT_L		(uint8_t)0x46
#define	GYRO_ZOUT_H		(uint8_t)0x47
#define	GYRO_ZOUT_L		(uint8_t)0x48

#define MAG_XOUT_L		(uint8_t)0x03
#define MAG_XOUT_H		(uint8_t)0x04
#define MAG_YOUT_L		(uint8_t)0x05
#define MAG_YOUT_H		(uint8_t)0x06
#define MAG_ZOUT_L		(uint8_t)0x07
#define MAG_ZOUT_H		(uint8_t)0x08
//--------------------other reg addr-----------------------//
#define	PWR_MGMT_1		    (uint8_t)0x6B
#define PWR_MGMT_2          (uint8_t)0x6C
#define	WHO_AM_I		    (uint8_t)0x75

#define EXT_SENS_DATA_00    (uint8_t)0x49
#define EXT_SENS_DATA_01    (uint8_t)0x4a
#define EXT_SENS_DATA_02    (uint8_t)0x4b
#define EXT_SENS_DATA_03    (uint8_t)0x4c

typedef enum __MPU9250_AccelRange {
	MPU9250_Accel_Range_2G = 0x00,
	MPU9250_Accel_Range_4G = 0x08,
	MPU9250_Accel_Range_8G = 0x10,
	MPU9250_Accel_Range_16G = 0x18
/*
 * 0b00000000 for +-2G
 * 0b00001000 for +-4G
 * 0b00010000 for +-8G
 * 0b00011000 for +-16G
 * */
} MPU9250_AccelRange;

typedef enum __MPU9250_GyroRange {
	MPU9250_Gyro_Range_250dps = 0x00,
	MPU9250_Gyro_Range_500dps = 0x08,
	MPU9250_Gyro_Range_1000dps = 0x10,
	MPU9250_Gyro_Range_2000dps = 0x18
/*
 * 0b00000000 for 250dps
 * 0b00001000 for 500dps
 * 0b00010000 for 1000dps
 * 0b00011000 for 2000dps
 * */
} MPU9250_GyroRange;

typedef enum __MPU9250_Accel_DLPFBandwidth {
	// set reg29 Bit[3] & Bit[2:0]
	// Hz
	MPU9250_Accel_DLPFBandwidth_460 = 0x00,    // delay 1.94ms
	MPU9250_Accel_DLPFBandwidth_184,           // delay 5.80ms
	MPU9250_Accel_DLPFBandwidth_92,            // delay 7.80ms
	MPU9250_Accel_DLPFBandwidth_41,            // delay 11.80ms
	MPU9250_Accel_DLPFBandwidth_20,            // delay 19.80ms
	MPU9250_Accel_DLPFBandwidth_10,            // delay 35.70ms
	MPU9250_Accel_DLPFBandwidth_5,             // delay 66.96ms
	MPU9250_Accel_DLPFBandwidth_460_2,         // delay 1.94ms
	MPU9250_Accel_DLPFBandwidth_1_13k = 0x08,  // delay 0.75ms

} MPU9250_Accel_DLPFBandwidth;

typedef enum __MPU9250_Accel_SampleRateDivider {
	// check reg30, when use lower power mode
	// Hz
	// Bandwidth 1.1kHz, Delay 1ms
	MPU9250_LP_ACCEL_ODR_0_24HZ = 0x00,
	MPU9250_LP_ACCEL_ODR_0_49HZ,
	MPU9250_LP_ACCEL_ODR_0_98HZ,
	MPU9250_LP_ACCEL_ODR_1_95HZ,
	MPU9250_LP_ACCEL_ODR_3_91HZ,
	MPU9250_LP_ACCEL_ODR_7_81HZ,
	MPU9250_LP_ACCEL_ODR_15_63HZ,
	MPU9250_LP_ACCEL_ODR_31_25HZ,
	MPU9250_LP_ACCEL_ODR_62_50HZ,
	MPU9250_LP_ACCEL_ODR_125HZ,
	MPU9250_LP_ACCEL_ODR_250HZ,
	MPU9250_LP_ACCEL_ODR_500HZ
} MPU9250_Accel_SampleRateDivider;

typedef enum __MPU9250_Gyro_DLPFBandwidth {
	// to use following 2 options, reg27 Bit[1:0](fchoice_b) must be set
	// these to options are not related to reg26 Bit[2:0](DLPF_CFG), but reg27 Bit[1:0](fchoice_b)
	// notice: this options can also affect temperature sensor
	// Hz
	MPU9250_Gyro_DLPFBandwidth_8800_x = 0x00,    // delay 0.064ms
	MPU9250_Gyro_DLPFBandwidth_3600_x = 0x00,    // delay 0.11ms

	// follow options can be set to reg26 Bit[2:0](DLPF_CFG) to set DLPFBandwidth
	MPU9250_Gyro_DLPFBandwidth_250 = 0x00,     // delay 0.97ms
	MPU9250_Gyro_DLPFBandwidth_184,            // delay 2.9ms
	MPU9250_Gyro_DLPFBandwidth_92,             // delay 3.9ms
	MPU9250_Gyro_DLPFBandwidth_41,             // delay 5.9ms
	MPU9250_Gyro_DLPFBandwidth_20,             // delay 9.9ms
	MPU9250_Gyro_DLPFBandwidth_10,             // delay 17.85ms
	MPU9250_Gyro_DLPFBandwidth_5,              // delay 33.48ms
	MPU9250_Gyro_DLPFBandwidth_3600,           // delay 0.17ms

} MPU9250_Gyro_DLPFBandwidth;

typedef struct __MPU9250_PropTypDef {

// reserved, maybe useless

} MPU9250_PropTypeDef;

typedef struct __MPU9250_DataTypeDef {
	// origin data from mpu9250
	volatile int16_t Accel_row[3];
	volatile int16_t Gyro_row[3];
	volatile int16_t Magn_row[3];
	// store real data in float
	float Accel[3];
	float Gyro[3];
	float Magn[3];

} MPU9250_DataTypeDef;

typedef struct __MPU9250 {
	MPU9250_PropTypeDef mpu_prop;
	MPU9250_DataTypeDef mpu_data;
} MPU9250;

uint8_t mpu_r_ak8963_WhoAmI(MPU9250 *mpu);
uint8_t mpu_r_WhoAmI(MPU9250 *mpu);
uint8_t MPU9250_Init(MPU9250 *mpu);
void MPU9250_ReadAccel(MPU9250 *mpu);
void MPU9250_ReadGyro(MPU9250 *mpu);
void MPU9250_ReadMag(MPU9250 *mpu);

#ifdef __cplusplus
}
#endif

#endif

mpu9250.c

/*
 ************ https://github.com/sin1111yi ************
 ******************************************************
 *        .__       ____ ____ ____ ____        .__
 *   _____|__| ____/_   /_   /_   /_   |___.__.|__|
 *  /  ___/  |/    \|   ||   ||   ||   <   |  ||  |
 *  \___ \|  |   |  \   ||   ||   ||   |\___  ||  |
 *  /___  >__|___|  /___||___||___||___|/ ____||__|
 *      \/        \/                    \/
 ******************************************************
 * @beginning
 * I don't like over encapsulation, low freedom and less api.
 * After read several projects on Github, I decided to write it by myself.
 *
 * @suggestions
 * If someone want to use SPI to drive MPU9250 to read 9DOF data,
 * I'll suggest him/her to use MPU6500 and another independent magnetometer chip.
 * Because MPU9250 is MPU6500 & AK8963, and AK8963 doesn't support SPI serial.
 * AK8963 must drive by I2C serial, which is put in MPU9250 connect MPU6500 and AK8963.
 * That means to drive AK8963, I need use SPI to drive MPU6500's I2C to drive AK8963.
 * That's troublesome. But it can be much more easier when using I2C.
 *
 * @my own view
 * I think it is stupid to use MPU9250, if PCB space and capital are enough.
 * And I think the designers of MPU9250 are really lazy, cause they name their registers in many different ways.
 *
 * @other
 * Those words above only represent myself. If someone have different views, please ignore my views.
 *
 *
 * @author  sin1111yi
 * @date    2021/12/9   restructure version 1
 *          2021/12/10  restructure version 2
 *
 * @refer   https://github.com/desertkun/MPU9250
 *          https://blog.csdn.net/liuyifanliu/article/details/99309839
 *
 */

#include "mpu9250.h"

#define mpu_select()    HAL_GPIO_WritePin(MPU9250_CS_GPIO, MPU9250_CS_PIN, GPIO_PIN_RESET)
#define mpu_deselect()  HAL_GPIO_WritePin(MPU9250_CS_GPIO, MPU9250_CS_PIN, GPIO_PIN_SET)

#define DATABUF_SIZ     16

static uint8_t dataBuf[DATABUF_SIZ] = { 0 }; // all read data will store in this array
static uint16_t i = 0; // loop control

/*** basic spi operate ***/
/*** I want to make these functions can be reused ***/
/*
 * @brief   write a byte through SPI and read feedback
 * @param   byte: byte to write
 * @return  received byte
 * */
static uint8_t spi_wr_byte(SPI_HandleTypeDef *hspi, uint8_t byte) {
	uint8_t feedback = 0;

	// wait SPI serial free
	while (HAL_SPI_GetState(hspi) == HAL_SPI_STATE_BUSY_TX_RX)
		;

	if (HAL_SPI_TransmitReceive(hspi, &byte, &feedback, 1, 0x01f4) != HAL_OK) {
		return 0xff;
	}

	return feedback;
}
/*
 * @brief   write several bytes though spi
 * @param   address: address of the first reg
 * @param   bytes: number of bytes to write
 * @param   num: number of bytes
 * */
static void spi_w_bytes(SPI_HandleTypeDef *hspi, uint8_t address,
		uint8_t *bytes, uint16_t num) {
	mpu_select();

	spi_wr_byte(hspi, address);
	for (i = 0; i < num; i++)
		spi_wr_byte(hspi, bytes[i]);

	mpu_deselect();
}

/*
 * @brief   read several bytes though spi
 * @param   address: address of the first reg
 * @param   num: number of bytes to read, number < DATABUF_SIZ
 * @return  data read array
 * */
static void spi_r_bytes(SPI_HandleTypeDef *hspi, uint8_t address, uint8_t num) {
	uint8_t _address = address | 0x80;

	mpu_select();

	// may be can use HAL_SPI_TransmitReceive()
	HAL_SPI_Transmit(hspi, &_address, 1, 0x01f4);
	HAL_SPI_Receive(hspi, dataBuf, num, 0x01f4); // store read data to dataBuf

	mpu_deselect();

}
/*** basic mpu9250 operate ***/
/*
 * @brief   write mpu9250 reg though spi
 * @param   address: address of reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_reg(uint8_t address, uint8_t byte) {
	spi_w_bytes(&MPU9250_SPI, address, &byte, 1);
}

/*
 * @brief   read mpu9250 regs though spi
 * @param   address: address of reg to write
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_regs(uint8_t address, uint8_t num) {
	spi_r_bytes(&MPU9250_SPI, address, num);
}
/******* basic ak8963 operate *******/
/*
 * @brief   write AK8963 regs though I2C in MPU9250
 * @param   address: address of AK8963 reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_ak8963_reg(uint8_t address, uint8_t byte) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_DO, byte);
	mpu_w_reg(I2C_SLV0_CTRL, 0x81);
}
/*
 * @brief read AK8963 regs though I2C in MPU9250
 * @param   address: first address of AK8963 regs to read
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_ak8963_regs(uint8_t address, uint8_t num) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR | 0x80);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_CTRL, 0x80 | num);
	HAL_Delay(1);
	mpu_r_regs(EXT_SENS_DATA_00, num);
}
/*
 * @brief   read ak8963 WHO_AM_I reg
 * @return  AK8963 WHO_AM_I value, expected to be 0x48
 * */
uint8_t mpu_r_ak8963_WhoAmI(MPU9250 *mpu) {
	mpu_r_ak8963_regs(AK8963_WHOAMI_REG, 1);
	return dataBuf[0];
}
/*
 * @brief   read mpu9250(mpu6500) WHO_AM_I reg
 * @return  mpu9250(mpu6500) WHO_AM_I value, expected to be 0x48
 * */
uint8_t mpu_r_WhoAmI(MPU9250 *mpu) {
	mpu_r_regs(WHO_AM_I, 1);
	return dataBuf[0];
}
/*
 * @brief   init origin data
 * */
static void MPU9250_StructInit(MPU9250 *mpu) {

	for (uint8_t i = 0; i < 3; i++) {
		mpu->mpu_data.Accel[i] = 0;
		mpu->mpu_data.Gyro[i] = 0;
		mpu->mpu_data.Magn[i] = 0;

		mpu->mpu_data.Accel_row[i] = 0;
		mpu->mpu_data.Gyro_row[i] = 0;
		mpu->mpu_data.Magn_row[i] = 0.0;
	}
}
/*
 * @brief   init mpu9250
 * */
uint8_t MPU9250_Init(MPU9250 *mpu) {
	MPU9250_StructInit(mpu);

	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x80); // reset MPU9250, reg107
	HAL_Delay(10);
	mpu_w_reg(USER_CTRL, (uint8_t) 0x20); // enable I2C master mode, reg106
	mpu_w_reg(I2C_MST_CTRL, (uint8_t) 0x0D); // set I2C clock speed to 400kHz, reg36
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x00); // set AK8963 to power down
	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x80); // reset MPU9250, Bit[7] will auto clear
	HAL_Delay(10);
	mpu_w_ak8963_reg(AK8963_CNTL2_REG, AK8963_CNTL2_SRST); // reset AK8963
	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x01); // select clock source
	mpu_w_reg(PWR_MGMT_2, (uint8_t) 0x00); // enable accel and gyro

	/* init GYRO and ACCEL */
	mpu_w_reg(SMPLRT_DIV, (uint8_t) 0x00); // SAMPLE_RATE= Internal_Sample_Rate / (1 + SMPLRT_DIV), Internal_Sample_Rate==8K
	mpu_w_reg(GYRO_CONFIG, (uint8_t) MPU9250_Gyro_Range_2000dps); // gyro full scale select
	mpu_w_reg(ACCEL_CONFIG, (uint8_t) MPU9250_Accel_Range_16G); // accel full scale select
	mpu_w_reg(ACCEL_CONFIG_2, (uint8_t) MPU9250_Accel_DLPFBandwidth_460);
	mpu_w_reg(CONFIG, (uint8_t) MPU9250_Gyro_DLPFBandwidth_250);
	/* init MAG */
	mpu_w_reg(USER_CTRL, (uint8_t) 0x20); // enable I2C master mode
	mpu_w_reg(I2C_MST_CTRL, (uint8_t) 0x0D); // set I2C clock speed to 400kHz, reg36
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x00); // set AK8963 to power down
	HAL_Delay(100);
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x0f); // set AK8963 to Fuse ROM access mode
	HAL_Delay(100);
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x00); // set AK8963 to power down
	HAL_Delay(100);
	mpu_w_ak8963_reg(AK8963_CNTL1_REG, (uint8_t) 0x16); // AK8963 working on Continuous measurement mode 2 & 16-bit output
	HAL_Delay(100);
	mpu_w_reg(PWR_MGMT_1, (uint8_t) 0x01); // select clock source
	mpu_r_ak8963_regs(MAG_XOUT_L, 7);

	return 0x00;
}
/*
 * @brief   read accel origin value and calculate real value
 *          data will be stored in mpu
 * */
void MPU9250_ReadAccel(MPU9250 *mpu) {
	// m/s
	mpu_r_regs(ACCEL_XOUT_H, 6);
	// calculate x axis
	mpu->mpu_data.Accel_row[0] = (dataBuf[0] << 8) | dataBuf[1];
	mpu->mpu_data.Accel[0] = (float) mpu->mpu_data.Accel_row[0] / 208.980;

	// calculate y axis
	mpu->mpu_data.Accel_row[1] = (dataBuf[2] << 8) | dataBuf[3];
	mpu->mpu_data.Accel[1] = (float) mpu->mpu_data.Accel_row[1] / 208.980;

	// calculate z axis
	mpu->mpu_data.Accel_row[2] = (dataBuf[4] << 8) | dataBuf[5];
	mpu->mpu_data.Accel[2] = (float) mpu->mpu_data.Accel_row[2] / 208.980;
}
/*
 * @brief   read gyro origin value and calculate real value
 *          data will be stored in mpu
 * */
void MPU9250_ReadGyro(MPU9250 *mpu) {
	// d/s
	mpu_r_regs(GYRO_XOUT_H, 6);
	// calculate x axis
	mpu->mpu_data.Gyro_row[0] = (dataBuf[0] << 8) | dataBuf[1];
	mpu->mpu_data.Gyro[0] = mpu->mpu_data.Gyro_row[0] / 16.384;

	// calculate y axis
	mpu->mpu_data.Gyro_row[1] = (dataBuf[2] << 8) | dataBuf[3];
	mpu->mpu_data.Gyro[1] = mpu->mpu_data.Gyro_row[1] / 16.384;

	// calculate z axis
	mpu->mpu_data.Gyro_row[2] = (dataBuf[4] << 8) | dataBuf[5];
	mpu->mpu_data.Gyro[2] = mpu->mpu_data.Gyro_row[2] / 16.384;
}
/*
 * @brief   read mag origin value and calculate real value
 *          data will be stored in mpu
 *
 * @notice  this part may have some bugs, but I guess my GY-91 is broken
 *          until I get my new module, I can't judge where the problem is
 * */
void MPU9250_ReadMag(MPU9250 *mpu) {
	uint8_t mag_adjust[3] = { 0 };
	uint8_t mag_buffer[6] = { 0 };

	mpu_r_ak8963_regs(AK8963_ASAX, 3);
	mag_adjust[0] = dataBuf[0];
	mag_adjust[1] = dataBuf[1];
	mag_adjust[2] = dataBuf[2];

	// read AK8963_ST2_REG is necessary
	// ST2 register has a role as data reading end register(on page 51)

	mpu_r_ak8963_regs(MAG_XOUT_L, 1);
	mag_buffer[0] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1); // data read finish reg
	mpu_r_ak8963_regs(MAG_XOUT_H, 1);
	mag_buffer[1] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);

	mpu_r_ak8963_regs(MAG_YOUT_L, 1);
	mag_buffer[2] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);
	mpu_r_ak8963_regs(MAG_YOUT_H, 1);
	mag_buffer[3] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);

	mpu_r_ak8963_regs(MAG_ZOUT_L, 1);
	mag_buffer[4] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);
	mpu_r_ak8963_regs(MAG_ZOUT_H, 1);
	mag_buffer[5] = dataBuf[0];
	mpu_r_ak8963_regs(AK8963_ST2_REG, 1);


	mpu->mpu_data.Magn_row[0] = (mag_buffer[1] << 8) | mag_buffer[0];
	mpu->mpu_data.Magn_row[1] = (mag_buffer[3] << 8) | mag_buffer[2];
	mpu->mpu_data.Magn_row[2] = (mag_buffer[5] << 8) | mag_buffer[4];

	// calculate real value, check page53
	mpu->mpu_data.Magn[0] = (float) mpu->mpu_data.Magn_row[0]
			* (((mag_adjust[0] - 128) / 256.0) + 1);
	mpu->mpu_data.Magn[0] = mpu->mpu_data.Magn_row[0] * 0.15;

	mpu->mpu_data.Magn[1] = (float) mpu->mpu_data.Magn_row[1]
			* (((mag_adjust[1] - 128) / 256.0) + 1);
	mpu->mpu_data.Magn[1] = mpu->mpu_data.Magn_row[1] * 0.15;

	mpu->mpu_data.Magn[2] = (float) mpu->mpu_data.Magn_row[2]
			* (((mag_adjust[2] - 128) / 256.0) + 1);
	mpu->mpu_data.Magn[2] = mpu->mpu_data.Magn_row[2] * 0.15;
}

详细说明

基础SPI驱动

包括读写、读、写

/*** basic spi operate ***/
/*** I want to make these functions can be reused ***/
/*
 * @brief   write a byte through SPI and read feedback
 * @param   byte: byte to write
 * @return  received byte
 * */
static uint8_t spi_wr_byte(SPI_HandleTypeDef *hspi, uint8_t byte) {
	uint8_t feedback = 0;

	// wait SPI serial free
	while (HAL_SPI_GetState(hspi) == HAL_SPI_STATE_BUSY_TX_RX)
		;

	if (HAL_SPI_TransmitReceive(hspi, &byte, &feedback, 1, 0x01f4) != HAL_OK) {
		return 0xff;
	}

	return feedback;
}
/*
 * @brief   write several bytes though spi
 * @param   address: address of the first reg
 * @param   bytes: number of bytes to write
 * @param   num: number of bytes
 * */
static void spi_w_bytes(SPI_HandleTypeDef *hspi, uint8_t address,
		uint8_t *bytes, uint16_t num) {
	mpu_select();

	spi_wr_byte(hspi, address);
	for (i = 0; i < num; i++)
		spi_wr_byte(hspi, bytes[i]);

	mpu_deselect();
}

/*
 * @brief   read several bytes though spi
 * @param   address: address of the first reg
 * @param   num: number of bytes to read, number < DATABUF_SIZ
 * @return  data read array
 * */
static void spi_r_bytes(SPI_HandleTypeDef *hspi, uint8_t address, uint8_t num) {
	uint8_t _address = address | 0x80;

	mpu_select();

	// may be can use HAL_SPI_TransmitReceive()
	HAL_SPI_Transmit(hspi, &_address, 1, 0x01f4);
	HAL_SPI_Receive(hspi, dataBuf, num, 0x01f4); // store read data to dataBuf

	mpu_deselect();

}

基础MPU驱动

将基础SPI驱动部分代码封装在其中

/*** basic mpu9250 operate ***/
/*
 * @brief   write mpu9250 reg though spi
 * @param   address: address of reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_reg(uint8_t address, uint8_t byte) {
	spi_w_bytes(&MPU9250_SPI, address, &byte, 1);
}

/*
 * @brief   read mpu9250 regs though spi
 * @param   address: address of reg to write
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_regs(uint8_t address, uint8_t num) {
	spi_r_bytes(&MPU9250_SPI, address, num);
}

基础AK8963驱动

/******* basic ak8963 operate *******/
/*
 * @brief   write AK8963 regs though I2C in MPU9250
 * @param   address: address of AK8963 reg to write
 * @param   byte: byte to write
 * */
static void mpu_w_ak8963_reg(uint8_t address, uint8_t byte) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_DO, byte);
	mpu_w_reg(I2C_SLV0_CTRL, 0x81);
}
/*
 * @brief read AK8963 regs though I2C in MPU9250
 * @param   address: first address of AK8963 regs to read
 * @param   num: number of byte to read
 * @return  read bytes array
 * */
static void mpu_r_ak8963_regs(uint8_t address, uint8_t num) {
	mpu_w_reg(I2C_SLV0_ADDR, AK8963_I2C_ADDR | 0x80);
	mpu_w_reg(I2C_SLV0_REG, address);
	mpu_w_reg(I2C_SLV0_CTRL, 0x80 | num);
	HAL_Delay(1);
	mpu_r_regs(EXT_SENS_DATA_00, num);
}

寄存器说明

I2C_SLV0_DO寄存器
在这里插入图片描述
意思是如果想向I2C从设备0写入数据,只要写入这个寄存器即可,可以理解为向这个寄存器写数据即为向从设备0写数据。

I2C_SLV0_CTRL寄存器
在这里插入图片描述
在这里插入图片描述
根据上表就可知道如果需要从I2C从设备中读取数据就要需要给该寄存器第[7]位置1,读取的数据个数为第[3:0]位的值,如果需要从I2C从设备中读取1个字节,则要向该寄存器写入0b10000001,即十六进制的0x81,读取3个字节,则要向该寄存器写入0b10000011,即十六进制的0x83。
其他寄存器请查阅上面的代码注释以及官方的寄存器手册。

总结

买模块之前想清楚,拿到模块之后看清楚
多查文档,芯片文档才是开发的最好助力
多看别人的代码,别人的代码总能给到帮助
多运行,多调试,别人的代码不一定最好

Github

https://github.com/sin1111yi/stm32_mpu9250_spi_hal_lib

版权声明:本文为CSDN博主「sin1111yi」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sin1111yi/article/details/121906053

生成海报
点赞 0

sin1111yi

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

ESP8266与PCA9685通信I2C

ESP8266与PCA9685通信I2C Talk is cheap, show you code! /*** ESP8266与PCA9685通过I2C协议通信* 功能:控制PCA9685上的16个舵机旋转0-18

stm32——4、中断exti

这里是基于正点原子开发板的学习记录。 首先你要加入固件库 stm32f10x_exti.h 和 stm32f10x_exti.c 1、STM32中每个io口都可以作为外部中断的中断输入口。 2、STM32F103的中断控制器支持19个外部中断/