cancel
Showing results for 
Search instead for 
Did you mean: 

X-NUCLEO-IKS02A1 sensor orientation (axis) on PCB for use in MOTIONFX library

Pantera_MXJ
Associate II

Hello fellow users!

 

I'm trying to implement the Fusion MOTIONFX library with the board X-NUCLEO-IKS02A1 and the nucleo board NUCLEO-U575ZI-Q so I can output via terminal all the values.

 

So far I can output the fusion values but they are wrong as I need to define the sensors orientation on the PCB board (real world) and there is no documentation that indicates this for the IKS02A1 (I also have the IKS4A1 and such info also is not avilable)

 

So will be nice of ST to have a:


1)definitive guide or schematic showing the physical orientation of the sensor axes (ISM330DHCX, IIS2MDC and IIS2DLPC) on the X-NUCLEO-IKS02A1.

2) The recommended orientation strings (acc_orientation, gyro_orientation, mag_orientation) for this specific board.

3) Clarification: Does the user need to remap the sensor axes manually in code to match the declared orientation strings? Or does MotionFX internally handle axis rotation based on the strings?

4) Ideally: an official reference example with working axis mapping + MotionFX configuration for terminal output

 

So here is the part of the code that I cant figure out what is the right one (Point 2 Above) if someone can point to the right declaration will be great.

 

strcpy((char *)MotionFX_knobs.acc_orientation, "swu");  //South/West/Up
strcpy((char *)MotionFX_knobs.gyro_orientation, "swu");
strcpy((char *)MotionFX_knobs.mag_orientation, "enu"); //East/North/Up

 

The information of sensor placement and axes orientation should be in all X-Nucleo with MEMs sensors boards, even the axes printed on the PCB

 

And if this is the correct way to map the sensor axes (Point 3)

 

data_in.acc[0] = (float)acc_axes.x / 1000.0f;
data_in.acc[1] = (float)acc_axes.y / 1000.0f;
data_in.acc[2] = (float)acc_axes.z / 1000.0f;

data_in.gyro[0] = (float)gyro_axes.x / 1000.0f;
data_in.gyro[1] = (float)gyro_axes.y / 1000.0f;
data_in.gyro[2] = (float)gyro_axes.z / 1000.0f;

data_in.mag[0] = (float)mag_axes.x / 500.0f;
data_in.mag[1] = (float)mag_axes.y / 500.0f;
data_in.mag[2] = (float)mag_axes.z / 500.0f;

 

Any help is appreciated, if the test is code is needed I can post it as is based on the Datalog terminal output for my boards combination.

 

Kind Regards

1 ACCEPTED SOLUTION

Accepted Solutions
Pantera_MXJ
Associate II

Well, thanks to the tools available today (thanks AI)  the problem was solved, the problem was the different units I was using did not match what MOTIONFX was expecting and other calibration issues, hopefully this will help somebody in the future, here is the final code used:

 

/**
 ******************************************************************************
 * File Name          : app_mems.c
 * Description        : This file provides code for the configuration
 * of the STMicroelectronics.X-CUBE-MEMS1.11.3.0 instances.
 ******************************************************************************
 * @attention
 *
 * JMX Fusion Terminal JUL 17 2025
 * 
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "app_mems.h"
#include "main.h"
#include <stdio.h>
#include <string.h>

#define _USE_MATH_DEFINES
#include <math.h>

#include "iks02a1_motion_sensors.h"
#include "stm32u5xx_nucleo.h"
#include "motion_fx.h"
#include "iks02a1_mems_control_ex.h"

/* Private typedef -----------------------------------------------------------*/
typedef enum {
  APP_STATE_CALIBRATING,
  APP_STATE_RUNNING
} AppState_t;

/* Private define ------------------------------------------------------------*/
#define SAMPLING_FREQ_HZ           100
#define SAMPLING_PERIOD_MS         (1000 / SAMPLING_FREQ_HZ)
#define STATE_SIZE                 (2450)
#define PRINT_FREQUENCY_HZ         5
#define PRINT_INTERVAL_CYCLES      (SAMPLING_FREQ_HZ / PRINT_FREQUENCY_HZ)
#define GBIAS_ACC_TH_SC            (2.0f * 0.000765f)
#define GBIAS_GYRO_TH_SC           (2.0f * 0.002f)
#define GBIAS_MAG_TH_SC            (2.0f * 0.001500f)
#define DECIMATION                 1U
#define MAG_CAL_DIVISOR            500.0f

/* Private variables ---------------------------------------------------------*/
static MFX_knobs_t MotionFX_knobs;
static MFX_input_t  data_in;
static MFX_output_t data_out;
static uint8_t      mfxstate[STATE_SIZE];
static MFX_MagCal_input_t mag_data_in_cal;
static MFX_MagCal_output_t mag_cal_out;

static AppState_t   AppState = APP_STATE_CALIBRATING;
static uint32_t     print_counter = 0;
static IKS02A1_MOTION_SENSOR_Axes_t mag_axes;

/* Private function prototypes -----------------------------------------------*/
static void MX_IKS02A1_DataLogTerminal_Init(void);
static void MX_IKS02A1_DataLogTerminal_Process(void);
static void initialize_motion_fx(void);
static void print_full_output(void);
static void print_calibration_status(void);

void MX_MEMS_Init(void)
{
  MX_IKS02A1_DataLogTerminal_Init();
}

void MX_MEMS_Process(void)
{
  MX_IKS02A1_DataLogTerminal_Process();
}

/**
  * @brief  Initialize the application.
  */
void MX_IKS02A1_DataLogTerminal_Init(void)
{
  BSP_LED_Init(LED2);
  BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_EXTI);
  BSP_COM_Init(COM1);

  printf("-- STMicroelectronics JMX MotionFX Fusion DEMO --\r\n");

  IKS02A1_MOTION_SENSOR_Init(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO | MOTION_GYRO);
  IKS02A1_MOTION_SENSOR_Init(IKS02A1_IIS2MDC_0, MOTION_MAGNETO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_ISM330DHCX_0, MOTION_GYRO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_IIS2MDC_0, MOTION_MAGNETO);
  printf("Programando valores de Rango para ISM330DHCX Gyro/Acc...\r\n");
  IKS02A1_MOTION_SENSOR_SetFullScale(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, 4);
  IKS02A1_MOTION_SENSOR_SetFullScale(IKS02A1_ISM330DHCX_0, MOTION_GYRO, 500);
  int32_t fs_acc;
  int32_t fs_gyro;
  IKS02A1_MOTION_SENSOR_GetFullScale(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, &fs_acc);
  IKS02A1_MOTION_SENSOR_GetFullScale(IKS02A1_ISM330DHCX_0, MOTION_GYRO, &fs_gyro);
  printf("Accelerometer FSR set to: %ld g\r\n", fs_acc);
  printf("Gyroscope FSR set to: %ld dps\r\n", fs_gyro);


  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, SAMPLING_FREQ_HZ);
  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_ISM330DHCX_0, MOTION_GYRO, SAMPLING_FREQ_HZ);
  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_IIS2MDC_0, MOTION_MAGNETO, SAMPLING_FREQ_HZ);

  initialize_motion_fx();
}

/**
  * @brief  Main application process.
  */
void MX_IKS02A1_DataLogTerminal_Process(void)
{
    IKS02A1_MOTION_SENSOR_Axes_t acc_axes, gyro_axes;
    float delta_time = (float)SAMPLING_PERIOD_MS / 1000.0f;

    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, &acc_axes);
    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_ISM330DHCX_0, MOTION_GYRO,    &gyro_axes);
    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_IIS2MDC_0, MOTION_MAGNETO,    &mag_axes);

    data_in.acc[0]  = acc_axes.x / 1000.0f;
    data_in.acc[1]  = acc_axes.y / 1000.0f;
    data_in.acc[2]  = acc_axes.z / 1000.0f;

    data_in.gyro[0] = gyro_axes.x / 1000.0f;
    data_in.gyro[1] = gyro_axes.y / 1000.0f;
    data_in.gyro[2] = gyro_axes.z / 1000.0f;

    if (AppState == APP_STATE_CALIBRATING)
    {
        // Durante la calibración, alimentar la utilidad MagCal con datos en uT/50
        mag_data_in_cal.mag[0] = mag_axes.x / MAG_CAL_DIVISOR;
        mag_data_in_cal.mag[1] = mag_axes.y / MAG_CAL_DIVISOR;
        mag_data_in_cal.mag[2] = mag_axes.z / MAG_CAL_DIVISOR;
        mag_data_in_cal.time_stamp = HAL_GetTick();
        MotionFX_MagCal_run(&mag_data_in_cal);

        print_calibration_status();
        MotionFX_MagCal_getParams(&mag_cal_out);

        if (mag_cal_out.cal_quality == MFX_MAGCALGOOD)
        {
            printf("\r\n--- CALIBRACION COMPLETA! ---\r\n");
            printf("Iniciando salida de datos de fusion...\r\n\r\n");
            AppState = APP_STATE_RUNNING;
        }
    }
    else if (AppState == APP_STATE_RUNNING)
    {

        data_in.mag[0] = (mag_axes.x / MAG_CAL_DIVISOR) - mag_cal_out.hi_bias[0];
        data_in.mag[1] = (mag_axes.y / MAG_CAL_DIVISOR) - mag_cal_out.hi_bias[1];
        data_in.mag[2] = (mag_axes.z / MAG_CAL_DIVISOR) - mag_cal_out.hi_bias[2];
        MotionFX_propagate((MFXState_t *)mfxstate, &data_out, &data_in, &delta_time);
        MotionFX_update((MFXState_t *)mfxstate, &data_out, &data_in, &delta_time, NULL);

        print_counter++;
        if (print_counter >= PRINT_INTERVAL_CYCLES)
        {
            print_full_output();
            print_counter = 0;
        }
    }

    HAL_Delay(SAMPLING_PERIOD_MS);
}

/**
  * @brief  Initialize the MotionFX library and configure knobs.
  */
static void initialize_motion_fx(void)
{
    __HAL_RCC_CRC_CLK_ENABLE();
    MotionFX_initialize((MFXState_t *)mfxstate);

    MotionFX_getKnobs((MFXState_t *)mfxstate, &MotionFX_knobs);

    BSP_SENSOR_ACC_GetOrientation(MotionFX_knobs.acc_orientation);
    BSP_SENSOR_GYR_GetOrientation(MotionFX_knobs.gyro_orientation);
    BSP_SENSOR_MAG_GetOrientation(MotionFX_knobs.mag_orientation);

    MotionFX_knobs.gbias_acc_th_sc  = GBIAS_ACC_TH_SC;
    MotionFX_knobs.gbias_gyro_th_sc = GBIAS_GYRO_TH_SC;
    MotionFX_knobs.gbias_mag_th_sc  = GBIAS_MAG_TH_SC;
    MotionFX_knobs.LMode            = 1;
    MotionFX_knobs.modx             = DECIMATION;
    MotionFX_knobs.output_type      = MFX_ENGINE_OUTPUT_ENU;
    MotionFX_knobs.ATime = 0.2f;
    MotionFX_knobs.MTime = 0.1f;



    MotionFX_setKnobs((MFXState_t *)mfxstate, &MotionFX_knobs);

    printf("Orientations -> ACC:%s, GYR:%s, MAG:%s\r\n",
           MotionFX_knobs.acc_orientation,
           MotionFX_knobs.gyro_orientation,
           MotionFX_knobs.mag_orientation);
    printf("Fusion output type -> %s\r\n",
           (MotionFX_knobs.output_type == MFX_ENGINE_OUTPUT_ENU) ? "ENU" : "NED");

    MotionFX_enable_9X((MFXState_t *)mfxstate, MFX_ENGINE_ENABLE);

    MotionFX_MagCal_init(SAMPLING_PERIOD_MS, 1);

    printf("Slowly rotate the board in a figure-8 pattern to calibrate the magnetometer.\r\n\r\n");
}

/**
  * @brief  Print the full fused data output and diagnostic info.
  */
static void print_full_output(void)
{
    float quat_q0 = data_out.quaternion[0];
    float quat_q1 = data_out.quaternion[1];
    float quat_q2 = data_out.quaternion[2];
    float quat_q3 = data_out.quaternion[3];

    float rot_yaw   = data_out.rotation[0];
    float rot_pitch = data_out.rotation[1];
    float rot_roll  = data_out.rotation[2];

    float g_x = data_out.gravity[0];
    float g_y = data_out.gravity[1];
    float g_z = data_out.gravity[2];

    float la_x = data_out.linear_acceleration[0];
    float la_y = data_out.linear_acceleration[1];
    float la_z = data_out.linear_acceleration[2];

    float fused_heading = data_out.heading;
    float heading_error = data_out.headingErr;

    printf("Q(%.4f,%.4f,%.4f,%.4f) R(Y:%.4f,P:%.4f,R:%.4f) G_g(x:%.4f,y:%.4f,z:%.4f) LA_g(x:%.4f,y:%.4f,z:%.4f) H_error:%.3f Heading_Fused:%.2f\r\n",
           quat_q0, quat_q1, quat_q2, quat_q3,
           rot_yaw, rot_pitch, rot_roll,
           g_x, g_y, g_z,
           la_x, la_y, la_z,
		   heading_error, fused_heading);
}

/**
  * @brief  Print only the magnetometer calibration status.
  */
static void print_calibration_status(void)
{
    MFX_MagCal_output_t mag_cal_out_local;
    MotionFX_MagCal_getParams(&mag_cal_out_local);

    MFX_MagCal_quality_t cal_quality = mag_cal_out_local.cal_quality;
    char *quality_str;
    switch(cal_quality)
    {
      case MFX_MAGCALUNKNOWN: quality_str = "UNKNOWN"; break;
      case MFX_MAGCALPOOR:    quality_str = "POOR";    break;
      case MFX_MAGCALOK:      quality_str = "OK";      break;
      case MFX_MAGCALGOOD:    quality_str = "GOOD";    break;
      default:                quality_str = "ERROR";   break;
    }

    printf("Calibrating... Quality: %s         \r", quality_str);
}

#ifdef __cplusplus
}
#endif

 

View solution in original post

4 REPLIES 4
Pantera_MXJ
Associate II

Pantera_MXJ_0-1752560916970.png

I just had to really zoom into the chips, impossible to see letter but  I can see the markings of pin one on the board and the chip, I thought that I had something else to share but my tests so far are not going well as I don't know how to set my reference. The letters NWU and SEU is how I think I need to set them in the MOTIONFX library, but the test are not going good, what I do is Test the Fusion Heading (ism330/iism2mdc) against the raw reading of the Magnetometer in degrees they should be close but so far North is good using NWU/Gyro-Acc and NSU -MAG- the rest is 90degrees off, but I can fix it because I'm guessing.

Pantera_MXJ
Associate II

For example, the document Getting started with MotionFX sensor fusion library in X-CUBE-MEMS1
expansion for STM32Cube, gives the data for the IKS4 but where is for the rest of the boards? or in this case the IKS02A?

 

Pantera_MXJ_0-1752576413560.png

Pantera_MXJ_1-1752576439808.png

 

Pantera_MXJ
Associate II

After many hours and following some ST examples, the orientation string of the board is in the iks02a1_mems_control_ex.c right here:  (I just have 5 days with the board and I have never used STM32 before so excuse me if you see many errors)

 

#include "iks02a1_mems_control_ex.h"

/**
  * @brief  Get accelerometer sensor orientation
  * @PAram  Orientation Pointer to sensor orientation
  * @retval None
  */
void BSP_SENSOR_ACC_GetOrientation(char *Orientation)
{
  Orientation[0] = 's';
  Orientation[1] = 'e';
  Orientation[2] = 'u';
}

/**
  * @brief  Get gyroscope sensor orientation
  * @PAram  Orientation Pointer to sensor orientation
  * @retval None
  */
void BSP_SENSOR_GYR_GetOrientation(char *Orientation)
{
  Orientation[0] = 's';
  Orientation[1] = 'e';
  Orientation[2] = 'u';
}

/**
  * @brief  Get magnetometer sensor orientation
  * @PAram  Orientation Pointer to sensor orientation
  * @retval None
  */
void BSP_SENSOR_MAG_GetOrientation(char *Orientation)
{
  Orientation[0] = 'n';
  Orientation[1] = 'e';
  Orientation[2] = 'u';
}

So instead of define this manually I call the orientation in my code

 

    BSP_SENSOR_ACC_GetOrientation(MotionFX_knobs.acc_orientation);
    BSP_SENSOR_GYR_GetOrientation(MotionFX_knobs.gyro_orientation);
    BSP_SENSOR_MAG_GetOrientation(MotionFX_knobs.mag_orientation);

And then definie the type of output ENU or NED:

    //Fusion output convention (ENU) MFX_ENGINE_OUTPUT_ENU
    MotionFX_knobs.output_type      = MFX_ENGINE_OUTPUT_NED;

But even after this changes I can't get the same Fusion heading as the heading provided by the magnetomer and my output data do not match MEMs studio data output, here is the main code used in app_mems.c in CubeIDE, the code will wait till the magnetomer is calibrated doing an 8 and getting GOOD quality, it will print based on the desire rate set up in the program to not flood the screen with messages, but the ODR is 100Hz

 

**
  ******************************************************************************
  * File Name          : app_mems.c
  * Description        : This file provides code for the configuration
  *                       of the STMicroelectronics.X-CUBE-MEMS1.11.3.0 instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "app_mems.h"
#include "main.h"
#include <stdio.h>
#include <string.h>

#define _USE_MATH_DEFINES
#include <math.h>

#include "iks02a1_motion_sensors.h"
#include "stm32u5xx_nucleo.h"
#include "motion_fx.h"
#include "iks02a1_mems_control_ex.h"

/* Private typedef -----------------------------------------------------------*/
typedef enum {
  APP_STATE_CALIBRATING,
  APP_STATE_RUNNING
} AppState_t;

/* Private define ------------------------------------------------------------*/
#define SAMPLING_FREQ_HZ            100
#define SAMPLING_PERIOD_MS          (1000 / SAMPLING_FREQ_HZ)
#define STATE_SIZE                  (2450)

// --- NEW DEFINES FOR PRINTING FREQUENCY CONTROL ---
#define PRINT_FREQUENCY_HZ          5
#define PRINT_INTERVAL_CYCLES       (SAMPLING_FREQ_HZ / PRINT_FREQUENCY_HZ)
// --------------------------------------------------

// --- KNOBS PARAMETERS FROM UM2220 ---
#define GBIAS_ACC_TH_SC             (2.0f * 0.000765f)   /* accel bias threshold */
#define GBIAS_GYRO_TH_SC            (2.0f * 0.002f)      /* gyro bias threshold */
#define GBIAS_MAG_TH_SC             (2.0f * 0.001500f)   /* mag bias threshold */
#define DECIMATION                  1U                  /* no extra decimation */
// --------------------------------------------------

/* Private variables ---------------------------------------------------------*/
static MFX_knobs_t MotionFX_knobs;
static MFX_input_t  data_in;
static MFX_output_t data_out;
static uint8_t      mfxstate[STATE_SIZE];
static MFX_MagCal_input_t mag_data_in;

static AppState_t   AppState = APP_STATE_CALIBRATING;
static uint32_t     print_counter = 0;

/* Private function prototypes -----------------------------------------------*/
static void MX_IKS02A1_DataLogTerminal_Init(void);
static void MX_IKS02A1_DataLogTerminal_Process(void);
static void initialize_motion_fx(void);
static void print_full_output(void);
static void print_calibration_status(void);

void MX_MEMS_Init(void)
{
  MX_IKS02A1_DataLogTerminal_Init();
}

void MX_MEMS_Process(void)
{
  MX_IKS02A1_DataLogTerminal_Process();
}

/**
  * @brief  Initialize the application.
  */
void MX_IKS02A1_DataLogTerminal_Init(void)
{
  BSP_LED_Init(LED2);
  BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_EXTI);
  BSP_COM_Init(COM1);

  printf("-- STMicroelectronics MotionFX Fusion DEMO --\r\n");

  IKS02A1_MOTION_SENSOR_Init(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO | MOTION_GYRO);
  IKS02A1_MOTION_SENSOR_Init(IKS02A1_IIS2MDC_0, MOTION_MAGNETO);

  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_ISM330DHCX_0, MOTION_GYRO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_IIS2MDC_0, MOTION_MAGNETO);

  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, SAMPLING_FREQ_HZ);
  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_ISM330DHCX_0, MOTION_GYRO, SAMPLING_FREQ_HZ);
  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_IIS2MDC_0, MOTION_MAGNETO, SAMPLING_FREQ_HZ);

  initialize_motion_fx();
}

/**
  * @brief  Main application process.
  */
void MX_IKS02A1_DataLogTerminal_Process(void)
{
    IKS02A1_MOTION_SENSOR_Axes_t acc_axes, gyro_axes, mag_axes;
    float delta_time = (float)SAMPLING_PERIOD_MS / 1000.0f;

    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, &acc_axes);
    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_ISM330DHCX_0, MOTION_GYRO,    &gyro_axes);
    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_IIS2MDC_0, MOTION_MAGNETO,   &mag_axes);

    data_in.acc[0]  = acc_axes.x / 1000.0f;
    data_in.acc[1]  = acc_axes.y / 1000.0f;
    data_in.acc[2]  = acc_axes.z / 1000.0f;

    data_in.gyro[0] = gyro_axes.x / 1000.0f;
    data_in.gyro[1] = gyro_axes.y / 1000.0f;
    data_in.gyro[2] = gyro_axes.z / 1000.0f;

    data_in.mag[0]  = mag_axes.x  / 500.0f;
    data_in.mag[1]  = mag_axes.y  / 500.0f;
    data_in.mag[2]  = mag_axes.z  / 500.0f;

    mag_data_in.mag[0]       = data_in.mag[0];
    mag_data_in.mag[1]       = data_in.mag[1];
    mag_data_in.mag[2]       = data_in.mag[2];
    mag_data_in.time_stamp   = HAL_GetTick();
    MotionFX_MagCal_run(&mag_data_in);

    if (AppState == APP_STATE_CALIBRATING)
    {
        print_calibration_status();

        MFX_MagCal_output_t mag_cal_out;
        MotionFX_MagCal_getParams(&mag_cal_out);
        if (mag_cal_out.cal_quality == MFX_MAGCALGOOD)
        {
            printf("\r\n--- CALIBRACION COMPLETA! ---\r\n");
            printf("Iniciando salida de datos de fusion...\r\n\r\n");
            AppState = APP_STATE_RUNNING;
        }
    }
    else if (AppState == APP_STATE_RUNNING)
    {
        MotionFX_propagate((MFXState_t *)mfxstate, &data_out, &data_in, &delta_time);
        MotionFX_update((MFXState_t *)mfxstate, &data_out, &data_in, &delta_time, NULL);

        // --- PRINTING CONTROL LOGIC ---
        print_counter++;
        if (print_counter >= PRINT_INTERVAL_CYCLES)
        {
            print_full_output();
            print_counter = 0;
        }
        // ------------------------------
    }

    HAL_Delay(SAMPLING_PERIOD_MS);
}

/**
  * @brief  Initialize the MotionFX library and configure knobs.
  */
static void initialize_motion_fx(void)
{
    __HAL_RCC_CRC_CLK_ENABLE();
    MotionFX_initialize((MFXState_t *)mfxstate);

    // 1) Get default knobs
    MotionFX_getKnobs((MFXState_t *)mfxstate, &MotionFX_knobs);

    // 2) Sensor orientation from BSP
    BSP_SENSOR_ACC_GetOrientation(MotionFX_knobs.acc_orientation);
    BSP_SENSOR_GYR_GetOrientation(MotionFX_knobs.gyro_orientation);
    BSP_SENSOR_MAG_GetOrientation(MotionFX_knobs.mag_orientation);

    // 3) Bias thresholds (from UM2220)
    MotionFX_knobs.gbias_acc_th_sc  = GBIAS_ACC_TH_SC;
    MotionFX_knobs.gbias_gyro_th_sc = GBIAS_GYRO_TH_SC;
    MotionFX_knobs.gbias_mag_th_sc  = GBIAS_MAG_TH_SC;

    // 4) Gyro learning mode (static)
    MotionFX_knobs.LMode            = 1;

    // 5) Decimation
    MotionFX_knobs.modx             = DECIMATION;

    // 6) Fusion output convention (ENU) MFX_ENGINE_OUTPUT_ENU
    MotionFX_knobs.output_type      = MFX_ENGINE_OUTPUT_NED;

    // 7) Apply knobs
    MotionFX_setKnobs((MFXState_t *)mfxstate, &MotionFX_knobs);

    // 8) Inform user of orientation & output type
    printf("Orientations -> ACC:%s, GYR:%s, MAG:%s\r\n",
           MotionFX_knobs.acc_orientation,
           MotionFX_knobs.gyro_orientation,
           MotionFX_knobs.mag_orientation);
    printf("Fusion output type -> %s\r\n",
           (MotionFX_knobs.output_type == MFX_ENGINE_OUTPUT_ENU) ? "ENU" : "NED");

    // 9) Enable 9-axis fusion
    MotionFX_enable_9X((MFXState_t *)mfxstate, MFX_ENGINE_ENABLE);

    // 10) Magnetometer calibration init
    MotionFX_MagCal_init(SAMPLING_PERIOD_MS, 1);

    printf("Slowly rotate the board in a figure-8 pattern to calibrate the magnetometer.\r\n\r\n");
}

/**
  * @brief  Print the full fused data output and diagnostic info.
  */
static void print_full_output(void)
{
    float quat_q0 = data_out.quaternion[0];
    float quat_q1 = data_out.quaternion[1];
    float quat_q2 = data_out.quaternion[2];
    float quat_q3 = data_out.quaternion[3];

    float rot_yaw   = data_out.rotation[0];
    float rot_pitch = data_out.rotation[1];
    float rot_roll  = data_out.rotation[2];
  

    float g_x = data_out.gravity[0];
    float g_y = data_out.gravity[1];
    float g_z = data_out.gravity[2];

    float la_x = data_out.linear_acceleration[0];
    float la_y = data_out.linear_acceleration[1];
    float la_z = data_out.linear_acceleration[2];

    float fused_heading = data_out.heading;

    // Heading only from magnetometer
    MFX_MagCal_output_t mag_cal_out;
    MotionFX_MagCal_getParams(&mag_cal_out);
    float mag_x_cal = data_in.mag[0] - mag_cal_out.hi_bias[0];
    float mag_y_cal = data_in.mag[1] - mag_cal_out.hi_bias[1];
    float mag_head_rad = atan2f(mag_y_cal, mag_x_cal);
    float mag_head_deg = mag_head_rad * 180.0f / M_PI;
    if (mag_head_deg < 0) mag_head_deg += 360.0f;

    printf("Q(%.2f,%.2f,%.2f,%.2f) R(Y:%.2f,P:%.2f,R:%.2f) G(x:%.2f,y:%.2f,z:%.2f) LA(x:%.2f,y:%.2f,z:%.2f) H_Fused:%.2f H_MagOnly:%.2f\r\n",
           quat_q0, quat_q1, quat_q2, quat_q3,
           rot_yaw, rot_pitch, rot_roll,
           g_x, g_y, g_z,
           la_x, la_y, la_z,
           fused_heading, mag_head_deg);
}

/**
  * @brief  Print only the magnetometer calibration status.
  */
static void print_calibration_status(void)
{
    MFX_MagCal_output_t mag_cal_out;
    MotionFX_MagCal_getParams(&mag_cal_out);

    MFX_MagCal_quality_t cal_quality = mag_cal_out.cal_quality;
    char *quality_str;
    switch(cal_quality)
    {
      case MFX_MAGCALUNKNOWN: quality_str = "UNKNOWN"; break;
      case MFX_MAGCALPOOR:    quality_str = "POOR";    break;
      case MFX_MAGCALOK:      quality_str = "OK";      break;
      case MFX_MAGCALGOOD:    quality_str = "GOOD";    break;
      default:                quality_str = "ERROR";   break;
    }

    // Overwrite same line in terminal
    printf("Calibrating... Quality: %s        \r", quality_str);
}

#ifdef __cplusplus
}
#endif

 

I was testing the output between ENU and NED output, here is the NED output. The board is pointing North and the Fusion Heading and direction of some axes do not match the output of MEMs studio, the output is Quaternion, Rotation, Gravity, LinearAccel and Heading.

-- STMicroelectronics MotionFX Fusion DEMO --
Orientations -> ACC:seu, GYR:seu, MAG:neu
Fusion output type -> NED
Slowly rotate the board in a figure-8 pattern to calibrate the magnetometer.

Q(0.01,-0.03,-0.85,0.52) R(Y:243.30,P:-0.57,R:3.72) G(x:0.01,y:0.06,z:1.00) LA(x:-0.00,y:-0.00,z:-0.02) H_Fused:243.30 H_MagOnly:358.91
Q(0.01,-0.03,-0.85,0.52) R(Y:243.31,P:-0.56,R:3.72) G(x:0.01,y:0.06,z:1.00) LA(x:-0.00,y:-0.00,z:-0.02) H_Fused:243.31 H_MagOnly:357.28
Q(0.01,-0.03,-0.85,0.52) R(Y:243.31,P:-0.56,R:3.74) G(x:0.01,y:0.07,z:1.00) LA(x:-0.00,y:-0.00,z:-0.02) H_Fused:243.31 H_MagOnly:357.35

 

ENU output is more similar to the one I get in MEMs studio:

-- STMicroelectronics MotionFX Fusion DEMO --
Orientations -> ACC:seu, GYR:seu, MAG:neu
Fusion output type -> ENU
Slowly rotate the board in a figure-8 pattern to calibrate the magnetometer.

Q(-0.03,0.01,0.87,0.49) R(Y:238.56,P:0.51,R:-3.71) G(x:0.06,y:0.01,z:-1.00) LA(x:-0.00,y:-0.00,z:0.03) H_Fused:238.56 H_MagOnly:3.37
Q(-0.03,0.01,0.87,0.49) R(Y:238.56,P:0.50,R:-3.71) G(x:0.06,y:0.01,z:-1.00) LA(x:-0.00,y:-0.00,z:0.02) H_Fused:238.56 H_MagOnly:0.67
Q(-0.03,0.01,0.87,0.49) R(Y:238.56,P:0.51,R:-3.70) G(x:0.06,y:0.01,z:-1.00) LA(x:-0.00,y:-0.00,z:0.02) H_Fused:238.56 H_MagOnly:2.74

 

MEMs Studio (My terminal units vs MEMs studio are different for G and LinAcc)

Pantera_MXJ_0-1752640223371.png

Why I can't get the heading correctly? Why in my output the Yaw is wrong?

What am I doing wrong?

Kind Regards

Pantera_MXJ
Associate II

Well, thanks to the tools available today (thanks AI)  the problem was solved, the problem was the different units I was using did not match what MOTIONFX was expecting and other calibration issues, hopefully this will help somebody in the future, here is the final code used:

 

/**
 ******************************************************************************
 * File Name          : app_mems.c
 * Description        : This file provides code for the configuration
 * of the STMicroelectronics.X-CUBE-MEMS1.11.3.0 instances.
 ******************************************************************************
 * @attention
 *
 * JMX Fusion Terminal JUL 17 2025
 * 
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "app_mems.h"
#include "main.h"
#include <stdio.h>
#include <string.h>

#define _USE_MATH_DEFINES
#include <math.h>

#include "iks02a1_motion_sensors.h"
#include "stm32u5xx_nucleo.h"
#include "motion_fx.h"
#include "iks02a1_mems_control_ex.h"

/* Private typedef -----------------------------------------------------------*/
typedef enum {
  APP_STATE_CALIBRATING,
  APP_STATE_RUNNING
} AppState_t;

/* Private define ------------------------------------------------------------*/
#define SAMPLING_FREQ_HZ           100
#define SAMPLING_PERIOD_MS         (1000 / SAMPLING_FREQ_HZ)
#define STATE_SIZE                 (2450)
#define PRINT_FREQUENCY_HZ         5
#define PRINT_INTERVAL_CYCLES      (SAMPLING_FREQ_HZ / PRINT_FREQUENCY_HZ)
#define GBIAS_ACC_TH_SC            (2.0f * 0.000765f)
#define GBIAS_GYRO_TH_SC           (2.0f * 0.002f)
#define GBIAS_MAG_TH_SC            (2.0f * 0.001500f)
#define DECIMATION                 1U
#define MAG_CAL_DIVISOR            500.0f

/* Private variables ---------------------------------------------------------*/
static MFX_knobs_t MotionFX_knobs;
static MFX_input_t  data_in;
static MFX_output_t data_out;
static uint8_t      mfxstate[STATE_SIZE];
static MFX_MagCal_input_t mag_data_in_cal;
static MFX_MagCal_output_t mag_cal_out;

static AppState_t   AppState = APP_STATE_CALIBRATING;
static uint32_t     print_counter = 0;
static IKS02A1_MOTION_SENSOR_Axes_t mag_axes;

/* Private function prototypes -----------------------------------------------*/
static void MX_IKS02A1_DataLogTerminal_Init(void);
static void MX_IKS02A1_DataLogTerminal_Process(void);
static void initialize_motion_fx(void);
static void print_full_output(void);
static void print_calibration_status(void);

void MX_MEMS_Init(void)
{
  MX_IKS02A1_DataLogTerminal_Init();
}

void MX_MEMS_Process(void)
{
  MX_IKS02A1_DataLogTerminal_Process();
}

/**
  * @brief  Initialize the application.
  */
void MX_IKS02A1_DataLogTerminal_Init(void)
{
  BSP_LED_Init(LED2);
  BSP_PB_Init(BUTTON_KEY, BUTTON_MODE_EXTI);
  BSP_COM_Init(COM1);

  printf("-- STMicroelectronics JMX MotionFX Fusion DEMO --\r\n");

  IKS02A1_MOTION_SENSOR_Init(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO | MOTION_GYRO);
  IKS02A1_MOTION_SENSOR_Init(IKS02A1_IIS2MDC_0, MOTION_MAGNETO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_ISM330DHCX_0, MOTION_GYRO);
  IKS02A1_MOTION_SENSOR_Enable(IKS02A1_IIS2MDC_0, MOTION_MAGNETO);
  printf("Programando valores de Rango para ISM330DHCX Gyro/Acc...\r\n");
  IKS02A1_MOTION_SENSOR_SetFullScale(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, 4);
  IKS02A1_MOTION_SENSOR_SetFullScale(IKS02A1_ISM330DHCX_0, MOTION_GYRO, 500);
  int32_t fs_acc;
  int32_t fs_gyro;
  IKS02A1_MOTION_SENSOR_GetFullScale(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, &fs_acc);
  IKS02A1_MOTION_SENSOR_GetFullScale(IKS02A1_ISM330DHCX_0, MOTION_GYRO, &fs_gyro);
  printf("Accelerometer FSR set to: %ld g\r\n", fs_acc);
  printf("Gyroscope FSR set to: %ld dps\r\n", fs_gyro);


  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, SAMPLING_FREQ_HZ);
  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_ISM330DHCX_0, MOTION_GYRO, SAMPLING_FREQ_HZ);
  IKS02A1_MOTION_SENSOR_SetOutputDataRate(IKS02A1_IIS2MDC_0, MOTION_MAGNETO, SAMPLING_FREQ_HZ);

  initialize_motion_fx();
}

/**
  * @brief  Main application process.
  */
void MX_IKS02A1_DataLogTerminal_Process(void)
{
    IKS02A1_MOTION_SENSOR_Axes_t acc_axes, gyro_axes;
    float delta_time = (float)SAMPLING_PERIOD_MS / 1000.0f;

    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_ISM330DHCX_0, MOTION_ACCELERO, &acc_axes);
    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_ISM330DHCX_0, MOTION_GYRO,    &gyro_axes);
    IKS02A1_MOTION_SENSOR_GetAxes(IKS02A1_IIS2MDC_0, MOTION_MAGNETO,    &mag_axes);

    data_in.acc[0]  = acc_axes.x / 1000.0f;
    data_in.acc[1]  = acc_axes.y / 1000.0f;
    data_in.acc[2]  = acc_axes.z / 1000.0f;

    data_in.gyro[0] = gyro_axes.x / 1000.0f;
    data_in.gyro[1] = gyro_axes.y / 1000.0f;
    data_in.gyro[2] = gyro_axes.z / 1000.0f;

    if (AppState == APP_STATE_CALIBRATING)
    {
        // Durante la calibración, alimentar la utilidad MagCal con datos en uT/50
        mag_data_in_cal.mag[0] = mag_axes.x / MAG_CAL_DIVISOR;
        mag_data_in_cal.mag[1] = mag_axes.y / MAG_CAL_DIVISOR;
        mag_data_in_cal.mag[2] = mag_axes.z / MAG_CAL_DIVISOR;
        mag_data_in_cal.time_stamp = HAL_GetTick();
        MotionFX_MagCal_run(&mag_data_in_cal);

        print_calibration_status();
        MotionFX_MagCal_getParams(&mag_cal_out);

        if (mag_cal_out.cal_quality == MFX_MAGCALGOOD)
        {
            printf("\r\n--- CALIBRACION COMPLETA! ---\r\n");
            printf("Iniciando salida de datos de fusion...\r\n\r\n");
            AppState = APP_STATE_RUNNING;
        }
    }
    else if (AppState == APP_STATE_RUNNING)
    {

        data_in.mag[0] = (mag_axes.x / MAG_CAL_DIVISOR) - mag_cal_out.hi_bias[0];
        data_in.mag[1] = (mag_axes.y / MAG_CAL_DIVISOR) - mag_cal_out.hi_bias[1];
        data_in.mag[2] = (mag_axes.z / MAG_CAL_DIVISOR) - mag_cal_out.hi_bias[2];
        MotionFX_propagate((MFXState_t *)mfxstate, &data_out, &data_in, &delta_time);
        MotionFX_update((MFXState_t *)mfxstate, &data_out, &data_in, &delta_time, NULL);

        print_counter++;
        if (print_counter >= PRINT_INTERVAL_CYCLES)
        {
            print_full_output();
            print_counter = 0;
        }
    }

    HAL_Delay(SAMPLING_PERIOD_MS);
}

/**
  * @brief  Initialize the MotionFX library and configure knobs.
  */
static void initialize_motion_fx(void)
{
    __HAL_RCC_CRC_CLK_ENABLE();
    MotionFX_initialize((MFXState_t *)mfxstate);

    MotionFX_getKnobs((MFXState_t *)mfxstate, &MotionFX_knobs);

    BSP_SENSOR_ACC_GetOrientation(MotionFX_knobs.acc_orientation);
    BSP_SENSOR_GYR_GetOrientation(MotionFX_knobs.gyro_orientation);
    BSP_SENSOR_MAG_GetOrientation(MotionFX_knobs.mag_orientation);

    MotionFX_knobs.gbias_acc_th_sc  = GBIAS_ACC_TH_SC;
    MotionFX_knobs.gbias_gyro_th_sc = GBIAS_GYRO_TH_SC;
    MotionFX_knobs.gbias_mag_th_sc  = GBIAS_MAG_TH_SC;
    MotionFX_knobs.LMode            = 1;
    MotionFX_knobs.modx             = DECIMATION;
    MotionFX_knobs.output_type      = MFX_ENGINE_OUTPUT_ENU;
    MotionFX_knobs.ATime = 0.2f;
    MotionFX_knobs.MTime = 0.1f;



    MotionFX_setKnobs((MFXState_t *)mfxstate, &MotionFX_knobs);

    printf("Orientations -> ACC:%s, GYR:%s, MAG:%s\r\n",
           MotionFX_knobs.acc_orientation,
           MotionFX_knobs.gyro_orientation,
           MotionFX_knobs.mag_orientation);
    printf("Fusion output type -> %s\r\n",
           (MotionFX_knobs.output_type == MFX_ENGINE_OUTPUT_ENU) ? "ENU" : "NED");

    MotionFX_enable_9X((MFXState_t *)mfxstate, MFX_ENGINE_ENABLE);

    MotionFX_MagCal_init(SAMPLING_PERIOD_MS, 1);

    printf("Slowly rotate the board in a figure-8 pattern to calibrate the magnetometer.\r\n\r\n");
}

/**
  * @brief  Print the full fused data output and diagnostic info.
  */
static void print_full_output(void)
{
    float quat_q0 = data_out.quaternion[0];
    float quat_q1 = data_out.quaternion[1];
    float quat_q2 = data_out.quaternion[2];
    float quat_q3 = data_out.quaternion[3];

    float rot_yaw   = data_out.rotation[0];
    float rot_pitch = data_out.rotation[1];
    float rot_roll  = data_out.rotation[2];

    float g_x = data_out.gravity[0];
    float g_y = data_out.gravity[1];
    float g_z = data_out.gravity[2];

    float la_x = data_out.linear_acceleration[0];
    float la_y = data_out.linear_acceleration[1];
    float la_z = data_out.linear_acceleration[2];

    float fused_heading = data_out.heading;
    float heading_error = data_out.headingErr;

    printf("Q(%.4f,%.4f,%.4f,%.4f) R(Y:%.4f,P:%.4f,R:%.4f) G_g(x:%.4f,y:%.4f,z:%.4f) LA_g(x:%.4f,y:%.4f,z:%.4f) H_error:%.3f Heading_Fused:%.2f\r\n",
           quat_q0, quat_q1, quat_q2, quat_q3,
           rot_yaw, rot_pitch, rot_roll,
           g_x, g_y, g_z,
           la_x, la_y, la_z,
		   heading_error, fused_heading);
}

/**
  * @brief  Print only the magnetometer calibration status.
  */
static void print_calibration_status(void)
{
    MFX_MagCal_output_t mag_cal_out_local;
    MotionFX_MagCal_getParams(&mag_cal_out_local);

    MFX_MagCal_quality_t cal_quality = mag_cal_out_local.cal_quality;
    char *quality_str;
    switch(cal_quality)
    {
      case MFX_MAGCALUNKNOWN: quality_str = "UNKNOWN"; break;
      case MFX_MAGCALPOOR:    quality_str = "POOR";    break;
      case MFX_MAGCALOK:      quality_str = "OK";      break;
      case MFX_MAGCALGOOD:    quality_str = "GOOD";    break;
      default:                quality_str = "ERROR";   break;
    }

    printf("Calibrating... Quality: %s         \r", quality_str);
}

#ifdef __cplusplus
}
#endif