前言
使用的开发软件为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
暂无评论