Trying to use MPU9250 IMU sensor with my STM32G431RB NUCLEO board. The IMU's accelerometer and gyroscope are working fine. However, magnetometer gives constant values.
I think magnetometers power mode is set to read-once or off. However, I did not understand what the problem was.
On GitHub, I tried to implement the MPU9250 library that kriswiner wrote for Arduino. But I don't know whether I succeeded or failed.
The original code I implemented:
https://github.com/kriswiner/MPU9250/blob/master/MPU9250BasicAHRS.ino
The CubeIDE code I wrote for STM32, to configure and read magnetometer:
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <math.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
//Magnetometer Registers
#define AK8963_ADDRESS 0x0C<<1
#define AK8963_WHO_AM_I 0x00 // should return 0x48
#define AK8963_INFO 0x01
#define AK8963_ST1 0x02 // data ready status bit 0
#define AK8963_XOUT_L 0x03 // data
#define AK8963_XOUT_H 0x04
#define AK8963_YOUT_L 0x05
#define AK8963_YOUT_H 0x06
#define AK8963_ZOUT_L 0x07
#define AK8963_ZOUT_H 0x08
#define AK8963_ST2 0x09 // Data overflow bit 3 and data read error status bit 2
#define AK8963_CNTL 0x0A // Power down (0000), single-measurement (0001), self-test (1000) and Fuse ROM (1111) modes on bits 3:0
#define AK8963_ASTC 0x0C // Self test control
#define AK8963_I2CDIS 0x0F // I2C disable
#define AK8963_ASAX 0x10 // Fuse ROM x-axis sensitivity adjustment value
#define AK8963_ASAY 0x11 // Fuse ROM y-axis sensitivity adjustment value
#define AK8963_ASAZ 0x12 // Fuse ROM z-axis sensitivity adjustment value
#define WHO_AM_I_MPU9250 0x75 // Should return 0x71
#define MPU9250_ADDRESS 0x68<<1
#define INT_PIN_CFG 0x37
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
enum Mscale {
MFS_14BITS = 0, // 0.6 mG per LSB
MFS_16BITS // 0.15 mG per LSB
};
uint8_t Mscale = MFS_16BITS; // Choose either 14-bit or 16-bit magnetometer resolution
uint8_t Mmode = 0x02;
const uint16_t i2c_timeoutB = 100;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit(&hlpuart1, (uint8_t*)ptr, len, HAL_MAX_DELAY);
return len;
}
float MagnetoX;
float MagnetoY;
float MagnetoZ;
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_LPUART1_UART_Init();
MX_I2C3_Init();
MX_I2C2_Init();
/* USER CODE BEGIN 2 */
printf("**************************** \r\n");
printf("MPU9250 STM32 Implementation \r\n");
printf("**************************** \r\n");
//pre-def. vars
uint8_t readData;
uint8_t writeData;
// Check MPU and Mag---------------------------------------------------------------------------------------------------
//read MPU9255 WHOAMI
HAL_I2C_Mem_Read(&hi2c2, MPU9250_ADDRESS, WHO_AM_I_MPU9250, 1, &readData, 1, i2c_timeoutB);
printf("MPU WHO AM I is (Must return 113): %d\r\n", readData);
//enable Mag bypass
writeData = 0x22;
HAL_I2C_Mem_Write(&hi2c2, MPU9250_ADDRESS, INT_PIN_CFG, 1, &writeData, 1, i2c_timeoutB);
//read AK8963 WHOAMI
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_WHO_AM_I, 1, &readData, 1, i2c_timeoutB);
printf("MAG WHO AM I is (Must return 72): %d\r\n", readData);
printf("------------------------------------------------\r\n");
//Init Mag-------------------------------------------------------------------------------------------------------------
//Power down magnetometer
writeData = 0x00;
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
//Enter Fuse ROM access mode
writeData = 0x0F;
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
//Read the x-, y-, and z-axis calibration values
uint8_t rawMagCalData[3];
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_ASAX, 1, &rawMagCalData[0], 3, i2c_timeoutB);
float calMagX = (float)(rawMagCalData[0] - 128)/256. + 1.; // Return x-axis sensitivity adjustment values, etc.
float calMagY = (float)(rawMagCalData[1] - 128)/256. + 1.;
float calMagZ = (float)(rawMagCalData[2] - 128)/256. + 1.;
printf("Mag cal off X: %f\r\n", calMagX);
printf("Mag cal off Y: %f\r\n", calMagY);
printf("Mag cal off Z: %f\r\n", calMagZ);
HAL_Delay(100);
//Set magnetometer data resolution and sample ODR
writeData = Mscale << 4 | Mmode;
printf("writeData: %d\r\n", writeData);
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
printf("------------------------------------------------\r\n");
//Read Mag-------------------------------------------------------------------------------------------------------------
//Read Mag data
uint8_t rawMagData[6];
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_XOUT_L, 1, &rawMagData[0], 6, i2c_timeoutB);
float MagX = ((int16_t)rawMagData[1] << 8) | rawMagData[0];
float MagY = ((int16_t)rawMagData[3] << 8) | rawMagData[2];
float MagZ = ((int16_t)rawMagData[5] << 8) | rawMagData[4];
printf("Mag X: %f\r\n", MagX);
printf("Mag Y: %f\r\n", MagY);
printf("Mag Z: %f\r\n", MagZ);
HAL_Delay(100);
printf("------------------------------------------------\r\n");
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
//Read Mag data
uint8_t rawMagData[6];
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_XOUT_L, 1, &rawMagData[0], 6, i2c_timeoutB);
MagX = ((int16_t)rawMagData[1] << 8) | rawMagData[0];
MagY = ((int16_t)rawMagData[3] << 8) | rawMagData[2];
MagZ = ((int16_t)rawMagData[5] << 8) | rawMagData[4];
//Print to Com port via STLINK
printf("Mag X: %f\r\n", MagX);
printf("Mag Y: %f\r\n", MagY);
printf("Mag Z: %f\r\n", MagZ);
printf("------------------------------------------------\r\n");
HAL_Delay(1000);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
UART tab for readings:
MPU9250 and Mag.'s WHOAMI readings are fine.
When I studied the code a little more, I found that I made some critical mistakes.
After I post this question, I saw an article about reading data from MPU9250.
https://longnight975551865.wordpress.com/2018/02/11/how-to-read-data-from-mpu9250/
Although it is an article written for Arduino, it explained very well how register operations should be done in MPU9250 and especially how power management should be done. I saw that I had configured the AK8963 (magnetometer) incorrectly, and I realized that I was trying to read the data from the AK8963 incorrectly.
I will explain step by step my mistakes and solutions.
1)
After FUSE ROM access, I should have reset AK8963 but I continued without resetting.
Before writing the data resolution and sample ODR values, we need to reset the power mode by typing 0x00 into the AK8963_CNTL register.
//Power down magnetometer
writeData = 0x00;
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
Also, in the code that I shared, I was trying to read data as soon as I init the magnetometer, but this was also an inaccurate method because the way of reading the data is different compared to the accelerometer or gyroscope. That's why I deleted this part as well.
3)
The magnetometer's data reading process must be accurate. At first, it is necessary to read the register AK8963_ST1 and enable data updating.
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_ST1, 1, &readData, 1, i2c_timeoutB);
printf("Read Data: %d\r\n", readData);
printf("Read Data2: %d\r\n", (readData & 0x01));
if( (readData & 0x01) == 0x01 ){
//Read Mag data
uint8_t rawMagData[7];
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_XOUT_L, 1, &rawMagData[0], 7, i2c_timeoutB);
uint8_t c = rawMagData[6];
if(!(c & 0x08)) {
MagX = ((int16_t)rawMagData[1] << 8) | rawMagData[0];
MagY = ((int16_t)rawMagData[3] << 8) | rawMagData[2];
MagZ = ((int16_t)rawMagData[5] << 8) | rawMagData[4];
//Print to Com port via STLINK
printf("Mag X: %f\r\n", MagX);
printf("Mag Y: %f\r\n", MagY);
printf("Mag Z: %f\r\n", MagZ);
printf("------------------------------------------------\r\n");
}
}
else {
printf("No Data? \r\n");
printf("------------------------------------------------\r\n");
}
HAL_Delay(500);
New, updated code:
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <math.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
//Magnetometer Registers
#define AK8963_ADDRESS 0x0C<<1
#define AK8963_WHO_AM_I 0x00 // should return 0x48
#define AK8963_INFO 0x01
#define AK8963_ST1 0x02 // data ready status bit 0
#define AK8963_XOUT_L 0x03 // data
#define AK8963_XOUT_H 0x04
#define AK8963_YOUT_L 0x05
#define AK8963_YOUT_H 0x06
#define AK8963_ZOUT_L 0x07
#define AK8963_ZOUT_H 0x08
#define AK8963_ST2 0x09 // Data overflow bit 3 and data read error status bit 2
#define AK8963_CNTL 0x0A // Power down (0000), single-measurement (0001), self-test (1000) and Fuse ROM (1111) modes on bits 3:0
#define AK8963_ASTC 0x0C // Self test control
#define AK8963_I2CDIS 0x0F // I2C disable
#define AK8963_ASAX 0x10 // Fuse ROM x-axis sensitivity adjustment value
#define AK8963_ASAY 0x11 // Fuse ROM y-axis sensitivity adjustment value
#define AK8963_ASAZ 0x12 // Fuse ROM z-axis sensitivity adjustment value
#define WHO_AM_I_MPU9250 0x75 // Should return 0x71
#define MPU9250_ADDRESS 0x68<<1
#define INT_PIN_CFG 0x37
#define DATA_READY_MASK 0x01
#define MAGIC_OVERFLOW_MASK 0x8
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
float MagX;
float MagY;
float MagZ;
/* USER CODE BEGIN PV */
enum Mscale {
MFS_14BITS = 0, // 0.6 mG per LSB
MFS_16BITS // 0.15 mG per LSB
};
uint8_t Mscale = MFS_16BITS; // Choose either 14-bit or 16-bit magnetometer resolution
uint8_t Mmode = 0x02;
const uint16_t i2c_timeoutB = 100;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int _write(int file, char *ptr, int len)
{
HAL_UART_Transmit(&hlpuart1, (uint8_t*)ptr, len, HAL_MAX_DELAY);
return len;
}
float MagnetoX;
float MagnetoY;
float MagnetoZ;
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_LPUART1_UART_Init();
MX_I2C3_Init();
MX_I2C2_Init();
/* USER CODE BEGIN 2 */
printf("**************************** \r\n");
printf("MPU9250 STM32 Implementation \r\n");
printf("**************************** \r\n");
//pre-def. vars
uint8_t readData;
uint8_t writeData;
// Check MPU and Mag---------------------------------------------------------------------------------------------------
//read MPU9255 WHOAMI
HAL_I2C_Mem_Read(&hi2c2, MPU9250_ADDRESS, WHO_AM_I_MPU9250, 1, &readData, 1, i2c_timeoutB);
printf("MPU WHO AM I is (Must return 113): %d\r\n", readData);
//enable Mag bypass
writeData = 0x22;
HAL_I2C_Mem_Write(&hi2c2, MPU9250_ADDRESS, INT_PIN_CFG, 1, &writeData, 1, i2c_timeoutB);
//read AK8963 WHOAMI
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_WHO_AM_I, 1, &readData, 1, i2c_timeoutB);
printf("MAG WHO AM I is (Must return 72): %d\r\n", readData);
printf("------------------------------------------------\r\n");
//Init Mag-------------------------------------------------------------------------------------------------------------
//Power down magnetometer
writeData = 0x00;
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
//Enter Fuse ROM access mode
writeData = 0x0F;
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
//Read the x-, y-, and z-axis calibration values
uint8_t rawMagCalData[3];
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_ASAX, 1, &rawMagCalData[0], 3, i2c_timeoutB);
float calMagX = (float)(rawMagCalData[0] - 128)/256. + 1.; // Return x-axis sensitivity adjustment values, etc.
float calMagY = (float)(rawMagCalData[1] - 128)/256. + 1.;
float calMagZ = (float)(rawMagCalData[2] - 128)/256. + 1.;
printf("Mag cal off X: %f\r\n", calMagX);
printf("Mag cal off Y: %f\r\n", calMagY);
printf("Mag cal off Z: %f\r\n", calMagZ);
HAL_Delay(100);
//Power down magnetometer
writeData = 0x00;
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
//Set magnetometer data resolution and sample ODR
writeData = Mscale << 4 | 0x02;
//writeData = 0x16;
printf("writeData: %d\r\n", writeData);
HAL_I2C_Mem_Write(&hi2c2, AK8963_ADDRESS, AK8963_CNTL, 1, &writeData, 1, i2c_timeoutB);
HAL_Delay(100);
printf("------------------------------------------------\r\n");
/*
//Read Mag-------------------------------------------------------------------------------------------------------------
//Read Mag data
uint8_t rawMagData[6];
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_XOUT_L, 1, &rawMagData[0], 6, i2c_timeoutB);
MagX = ((int16_t)rawMagData[1] << 8) | rawMagData[0];
MagY = ((int16_t)rawMagData[3] << 8) | rawMagData[2];
MagZ = ((int16_t)rawMagData[5] << 8) | rawMagData[4];
printf("Mag X: %f\r\n", MagX);
printf("Mag Y: %f\r\n", MagY);
printf("Mag Z: %f\r\n", MagZ);
HAL_Delay(100);
printf("------------------------------------------------\r\n");
*/
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_ST1, 1, &readData, 1, i2c_timeoutB);
printf("Read Data: %d\r\n", readData);
printf("Read Data2: %d\r\n", (readData & 0x01));
if( (readData & 0x01) == 0x01 ){
//Read Mag data
uint8_t rawMagData[7];
HAL_I2C_Mem_Read(&hi2c2, AK8963_ADDRESS, AK8963_XOUT_L, 1, &rawMagData[0], 7, i2c_timeoutB);
uint8_t c = rawMagData[6];
if(!(c & 0x08)) {
MagX = ((int16_t)rawMagData[1] << 8) | rawMagData[0];
MagY = ((int16_t)rawMagData[3] << 8) | rawMagData[2];
MagZ = ((int16_t)rawMagData[5] << 8) | rawMagData[4];
//Print to Com port via STLINK
printf("Mag X: %f\r\n", MagX);
printf("Mag Y: %f\r\n", MagY);
printf("Mag Z: %f\r\n", MagZ);
printf("------------------------------------------------\r\n");
}
}
else {
printf("No Data? \r\n");
printf("------------------------------------------------\r\n");
}
HAL_Delay(500);
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
It's a simple mistake because I didn't read the datasheet truly. I hope it will be a good resource for people experiencing the same error. :)