cancel
Showing results for 
Search instead for 
Did you mean: 

How LSM6DSV16X enables sensor fusion low power (SFLP) algorithm

Denise SANFILIPPO
ST Employee

Summary      

The LSM6DSV16X device is the first 6-axis IMU that supports data fusion in a MEMS sensor. Sensor fusion is widely used in drones, wearables, TWS, AR/VR and other products. The sensor fusion algorithm can accurately identify the posture of objects in space motion. The LSM6DSV16X integrates a data fusion function, which aims to reduce the user’s algorithm development process and improve product competitiveness. This allows customers to quickly develop applications. The LSM6DSV16X adopts gyroscope dynamic calibration, greatly improving the stability of the algorithm and providing excellent performance.

 

1. How to compute data fusion based on an IMU?

Rotation matrices can be obtained from these three examples using matrix multiplication. For example, the product:

 

DeniseSANFILIPPO_0-1692960637154.png

Represents a rotation whose yaw, pitch, and roll angles are α, β and γ, respectively. More formally, it is an intrinsic rotation whose Tait–Bryan angles are α, β, γ, about axes z, y, x, respectively. Similarly, the product:

 

DeniseSANFILIPPO_1-1692960655994.png

 

2. What is the SFLP algorithm in the LSM6DSV16X?

A sensor fusion low-power (SFLP) block is available in the LSM6DSV16X for generating the following data based on the accelerometer and gyroscope data processing:

  1. Game rotation vector, which provides a quaternion representing the attitude of the device.
  2. Gravity vector, which provides a three-dimensional vector representing the direction of gravity.
  3. Gyroscope bias, which provides a three-dimensional vector representing the gyroscope bias.

Sensor fusion performance and time required to reach steady state

DeniseSANFILIPPO_2-1692960700823.png

 

3. SFLP demo and tools

The hardware setup is STEVAL-MKI109V3 and DIL24 adapter board STEVAL-MKI227KA.

STEVAL-MKI109V3STEVAL-MKI109V3DIL24 adapterDIL24 adapter

 

The software used is Unico-GUI.

DeniseSANFILIPPO_10-1692961168327.png

 

 

 

 

 

 

DeniseSANFILIPPO_8-1692961003433.png

 

 

 

 

 

 

 

 

 

When you shake the demo board, the image follows the actual position displayed. SFLP has integrated quaternion, so customers can directly get quaternion and convert it into euler angles.


4. Yaw angle automatic calibration flow

Due to the long-term operation of the gyroscope, the integration error caused by zero deviation becomes increasingly large. The yaw angle may experience significant drift due to lack of calibration of the magnetometer. To solve this problem, the SFLP algorithm adopts a dynamic self-calibration method to ensure yaw angle stability.

 

DeniseSANFILIPPO_11-1692961221302.png    


5. Automatic calibration flow

 

DeniseSANFILIPPO_12-1692961257945.png


6. How to configure the SFLP?

Coordinates:
Pitch rotation around the X-axisΩP
Roll rotation around the Y-axis ΩR
Heading/Yaw rotation around the Z-axisΩY

DeniseSANFILIPPO_14-1692961343963.png
  • Game rotation vector, which provides a quaternion representing the attitude of the device.
  • Gravity vector, which provides a three-dimensional vector representing the direction of gravity.
  • Gyroscope bias, which provides a three-dimensional vector representing the gyroscope bias.    

SFLP Register description:

Register bit config

Bit description

SFLP_GAME_EN

Enables SFLP

SFLP_GBIAS_FIFO_EN

Enables Gbias in FIFO mode

SFLP_GRAVITY_FIFO_EN

Enables gravity vector in FIFO mode

SFLP_GAME_FIFO_EN

Enables game rotation vector in FIFO mode

SFLP_GAME_ODR_[2:0]

Configures SFLP ODR

SFLP data format:

 

TAG

X_L

X_H

Y_L

Y_H

Z_L

Z_H

Axis format

Game rotation vector

13h

X

Y

Z

Half precision floating-point

Gyroscope bias       

16h

X

Y

Z

int16_t (raw, 125 dps sensitivity)              

Gravity vector           

17h

X

Y

Z

int16_t (raw, 2 g sensitivity)     

Code configuration:

  /* Check device ID */
  lsm6dsv16x_device_id_get(&dev_ctx, &whoamI);

  if (whoamI != LSM6DSV16X_ID)
    while (1);

  /* Restore default configuration */
  lsm6dsv16x_reset_set(&dev_ctx, LSM6DSV16X_RESTORE_CTRL_REGS);
  do {
    lsm6dsv16x_reset_get(&dev_ctx, &rst);
  } while (rst != LSM6DSV16X_READY);

  /* Enable Block Data Update */
  lsm6dsv16x_block_data_update_set(&dev_ctx, PROPERTY_ENABLE);
  /* Set full scale */
  lsm6dsv16x_xl_full_scale_set(&dev_ctx, LSM6DSV16X_4g);
  lsm6dsv16x_gy_full_scale_set(&dev_ctx, LSM6DSV16X_2000dps);

  /*
   * Set FIFO watermark (number of unread sensor data TAG + 6 bytes
   * stored in FIFO) to FIFO_WATERMARK samples
   */
  lsm6dsv16x_fifo_watermark_set(&dev_ctx, FIFO_WATERMARK);

  /* Set FIFO batch of sflp data */
  fifo_sflp.game_rotation = 1;
  fifo_sflp.gravity = 1;
  fifo_sflp.gbias = 1;
  lsm6dsv16x_fifo_sflp_batch_set(&dev_ctx, fifo_sflp);

  /* Set FIFO mode to Stream mode (aka Continuous Mode) */
  lsm6dsv16x_fifo_mode_set(&dev_ctx, LSM6DSV16X_STREAM_MODE);

  /* Set Output Data Rate */
  lsm6dsv16x_xl_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_30Hz);
  lsm6dsv16x_gy_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_30Hz);
  lsm6dsv16x_sflp_data_rate_set(&dev_ctx, LSM6DSV16X_SFLP_30Hz);

  lsm6dsv16x_sflp_game_rotation_set(&dev_ctx, PROPERTY_ENABLE);

More details about SFLP software are available on the GitHub repo sensor-fusion-code.


Conclusion

In this article, we described the principles of sensor fusion in the context of an inertial measurement unit and how to use sensor fusion in the LSM6DSV16X device.  The LSM6DSV16X integrates a complete set of acceleration and gyroscope algorithms in the sensor. This allows customers to obtain high-precision fusion algorithm results without specifying specific algorithm implementations.

Customers can use STEVAL-MKI109V3 and LSM6DSV16X to evaluate the SFLP performance and how to configure SFLP on the demo board. Users can quickly deploy algorithms, test, and evaluate performance.

You may also be interested in reading the following knowledge article: How to save power with LSM6DSV16X by leveraging on its adaptive self-configuration with MLC and FSM 

Comments
vincent3
Associate

Hi @Denise SANFILIPPO ,

does the SFLP uses the accelerometer offset registers (X_OFS_USR, Y_OFS_USR, Z_OFS_USR)?

Any chance you upgrade the algorithm by adding a magnetometer in the future?

Thanks

Denise SANFILIPPO
ST Employee

Hi @vincent3 ,

The SFLP does not use the accelerometer offset registers. 
At the moment, there are no plans to update the algorithm with the addition of a magnetometer.

By the way you can consider ST 6-axis IMU embedding ISPU, such as LSM6DSO16IS: you can find an example of 9-axis sensor fusion algorithm on the official GitHub which considers not only accelerometer and gyroscope data but also magnetometer data.

marti3110
Associate II

I am using a STM32F401RE board with the X-NUCLEO-IKS4A1 expansion board. I wrote the following code to get the quaternion values ​​and display them in MEMS Studio or Unicleo-GUI but after uploading the project to the board I cannot see the quaternion data on MEMS Studio or Unicleo-GUI. Am I doing something wrong?

 

"

/* USER CODE BEGIN Header */

#define SENSOR_BUS hi2c1

// Definizione del bus I2C (adattato per Nucleo STM32F401RE)

/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/

#include "main.h"

#include "app_mems.h"

#include "lsm6dsv16x_reg.h" // Include driver del sensore

#include <stdio.h>

#include <string.h>

 

// Dichiarazioni delle funzioni

int32_t LSM6DSV16X_FIFO_Get_Data(stmdev_ctx_t *ctx, uint8_t *data);

int32_t LSM6DSV16X_FIFO_Get_Tag(stmdev_ctx_t *ctx, uint8_t *tag);

 

static int32_t platform_write(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len);

static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len);

static void platform_delay(uint32_t ms);

 

/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

 

/* Private typedef -----------------------------------------------------------*/

/* USER CODE BEGIN PTD */

 

/* USER CODE END PTD */

 

/* Private define ------------------------------------------------------------*/

/* USER CODE BEGIN PD */

 

#define FIFO_WATERMARK 32

/* USER CODE END PD */

 

/* Private macro -------------------------------------------------------------*/

/* USER CODE BEGIN PM */

 

/* USER CODE END PM */

 

/* Private variables ---------------------------------------------------------*/

CRC_HandleTypeDef hcrc;

RTC_HandleTypeDef hrtc;

TIM_HandleTypeDef htim3;

 

/* USER CODE BEGIN PV */

 

static uint8_t whoamI;

static uint8_t tx_buffer[1000]; // Buffer per la trasmissione UART

 

extern I2C_HandleTypeDef hi2c1; // Dichiarazione del gestore I2C

 

static stmdev_ctx_t dev_ctx; // Definizione del contesto del sensore

 

// Variabili per il FIFO e per i quaternioni

static lsm6dsv16x_fifo_status_t fifo_status;

static lsm6dsv16x_fifo_sflp_raw_t fifo_sflp; // Creazione di un'istanza della struttura per le impostazioni del FIFO

float quaternion[4]; // Array per i dati del quaternione: w, x, y, z

 

/* USER CODE END PV */

 

/* Private function prototypes -----------------------------------------------*/

void SystemClock_Config(void);

static void MX_GPIO_Init(void);

static void MX_DMA_Init(void);

static void MX_CRC_Init(void);

static void MX_RTC_Init(void);

/* USER CODE BEGIN PFP */

static void tx_com( uint8_t *tx_buffer, uint16_t len );

static void sflp2q(float quat[4], uint16_t sflp[3]);

/* USER CODE END PFP */

 

/* Private user code ---------------------------------------------------------*/

/* USER CODE BEGIN 0 */

 

/* USER CODE END 0 */

 

/**

* @brief The application entry point.

* @retval int

*/

int main(void)

{

/* USER CODE BEGIN 1 */

 

 

 

/* USER CODE END 1 */

 

/* MCU Configuration--------------------------------------------------------*/

HAL_Init();

SystemClock_Config();

 

MX_GPIO_Init();

MX_DMA_Init();

MX_CRC_Init();

MX_RTC_Init();

MX_MEMS_Init();

 

/* USER CODE BEGIN 2 */

 

// Inizializzazione I2C per il sensore

dev_ctx.write_reg = platform_write; // Assegna la funzione di scrittura

dev_ctx.read_reg = platform_read; // Assegna la funzione di lettura

dev_ctx.mdelay = platform_delay;

dev_ctx.handle = &SENSOR_BUS; // Il tuo gestore I2C

 

 

 

/* Verifica l'ID del sensore */

lsm6dsv16x_device_id_get(&dev_ctx, &whoamI);

if (whoamI != LSM6DSV16X_ID) {

while (1); // Il sensore non è riconosciuto, fermati

}

 

 

// Ripristina la configurazione predefinita

lsm6dsv16x_reset_t rst;

lsm6dsv16x_reset_set(&dev_ctx, LSM6DSV16X_RESTORE_CTRL_REGS);

do {

lsm6dsv16x_reset_get(&dev_ctx, &rst);

} while (rst != LSM6DSV16X_READY);

 

// Abilita l'aggiornamento dei dati

lsm6dsv16x_block_data_update_set(&dev_ctx, PROPERTY_ENABLE);

 

// Imposta i range di full scale

lsm6dsv16x_xl_full_scale_set(&dev_ctx, LSM6DSV16X_4g);

lsm6dsv16x_gy_full_scale_set(&dev_ctx, LSM6DSV16X_2000dps);

 

// Imposta la FIFO watermark (numero di TAG di dati del sensore non letti + 6 byte memorizzati in FIFO) sui campioni FIFO_WATERMARK

lsm6dsv16x_fifo_watermark_set(&dev_ctx, FIFO_WATERMARK);

 

// Imposta batch FIFO di dati sflp

fifo_sflp.game_rotation = 1;

fifo_sflp.gravity = 0;

fifo_sflp.gbias = 0;

lsm6dsv16x_fifo_sflp_batch_set(&dev_ctx, fifo_sflp);

 

// Abilita il FIFO e impostalo in modalità streaming

lsm6dsv16x_fifo_mode_set(&dev_ctx, LSM6DSV16X_STREAM_MODE);

 

// Imposta ODR per accelerometro e giroscopio

lsm6dsv16x_xl_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_30Hz); // ODR dell'accelerometro

lsm6dsv16x_gy_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_30Hz); // ODR del giroscopio

lsm6dsv16x_sflp_data_rate_set(&dev_ctx, LSM6DSV16X_SFLP_30Hz);

lsm6dsv16x_sflp_game_rotation_set(&dev_ctx, PROPERTY_ENABLE);

 

 

/* USER CODE END 2 */

 

/* Infinite loop */

/* USER CODE BEGIN WHILE */

while (1)

{

// Controlla lo stato del FIFO

lsm6dsv16x_fifo_status_get(&dev_ctx, &fifo_status);

 

if (fifo_status.fifo_th == 1) {

// Leggi i dati dal FIFO

uint16_t num_samples = fifo_status.fifo_level;

 

while (num_samples--) {

lsm6dsv16x_fifo_out_raw_t raw_data;

lsm6dsv16x_fifo_out_raw_get(&dev_ctx, &raw_data);

 

// Se i dati sono del Game Rotation Vector

if (raw_data.tag == LSM6DSV16X_SFLP_GAME_ROTATION_VECTOR_TAG) {

// Converti i dati grezzi in quaternioni

sflp2q(quaternion, (uint16_t *)&raw_data.data[0]);

 

// Invia i dati via UART

snprintf((char *)tx_buffer, sizeof(tx_buffer), "Q1: %f, Q2: %f, Q3: %f, Q4: %f\n",

quaternion[0], quaternion[1], quaternion[2], quaternion[3]);

tx_com(tx_buffer, strlen((char const *)tx_buffer));

}

}

}

}

/* USER CODE END WHILE */

}

 

/**

* @brief System Clock Configuration

* @retval None

*/

void SystemClock_Config(void)

{

RCC_OscInitTypeDef RCC_OscInitStruct = {0};

RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

__HAL_RCC_PWR_CLK_ENABLE();

__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI;

RCC_OscInitStruct.HSIState = RCC_HSI_ON;

RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;

RCC_OscInitStruct.LSIState = RCC_LSI_ON;

RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;

RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;

RCC_OscInitStruct.PLL.PLLM = 16;

RCC_OscInitStruct.PLL.PLLN = 336;

RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV4;

RCC_OscInitStruct.PLL.PLLQ = 7;

if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)

{

Error_Handler();

}

RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK

|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;

RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;

RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;

RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;

RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)

{

Error_Handler();

}

}

 

/**

* @brief CRC Initialization Function

* @PAram None

* @retval None

*/

static void MX_CRC_Init(void)

{

hcrc.Instance = CRC;

if (HAL_CRC_Init(&hcrc) != HAL_OK)

{

Error_Handler();

}

}

 

/**

* @brief RTC Initialization Function

* @PAram None

* @retval None

*/

static void MX_RTC_Init(void)

{

hrtc.Instance = RTC;

hrtc.Init.HourFormat = RTC_HOURFORMAT_24;

hrtc.Init.AsynchPrediv = 127;

hrtc.Init.SynchPrediv = 255;

hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;

hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;

hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

if (HAL_RTC_Init(&hrtc) != HAL_OK)

{

Error_Handler();

}

}

 

/**

* @brief TIM3 Initialization Function

* @PAram None

* @retval None

*/

void MX_TIM3_Init(void)

{

TIM_ClockConfigTypeDef sClockSourceConfig = {0};

TIM_MasterConfigTypeDef sMasterConfig = {0};

htim3.Instance = TIM3;

htim3.Init.Prescaler = 0;

htim3.Init.CounterMode = TIM_COUNTERMODE_UP;

htim3.Init.Period = 65535;

htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;

htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;

if (HAL_TIM_Base_Init(&htim3) != HAL_OK)

{

Error_Handler();

}

sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;

if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)

{

Error_Handler();

}

sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)

{

Error_Handler();

}

}

 

/**

* Enable DMA controller clock

*/

static void MX_DMA_Init(void)

{

__HAL_RCC_DMA1_CLK_ENABLE();

HAL_NVIC_SetPriority(DMA1_Stream5_IRQn, 0, 0);

HAL_NVIC_EnableIRQ(DMA1_Stream5_IRQn);

}

 

/**

 

static void MX_GPIO_Init(void)

{

GPIO_InitTypeDef GPIO_InitStruct = {0};

__HAL_RCC_GPIOC_CLK_ENABLE();

__HAL_RCC_GPIOH_CLK_ENABLE();

__HAL_RCC_GPIOA_CLK_ENABLE();

__HAL_RCC_GPIOB_CLK_ENABLE();

GPIO_InitStruct.Pin = B1_Pin;

GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;

GPIO_InitStruct.Pull = GPIO_NOPULL;

HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

HAL_NVIC_SetPriority(EXTI15_10_IRQn, 0, 0);

HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);

}

 

/* USER CODE BEGIN 4 */

 

static int32_t platform_write(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len)

{

return HAL_I2C_Mem_Write(handle, LSM6DSV16X_I2C_ADD_L, reg, I2C_MEMADD_SIZE_8BIT, bufp, len, 1000);

}

 

 

static int32_t platform_read(void *handle, uint8_t reg, uint8_t *bufp, uint16_t len)

{

return HAL_I2C_Mem_Read(handle, LSM6DSV16X_I2C_ADD_L, reg, I2C_MEMADD_SIZE_8BIT, bufp, len, 1000);

}

 

 

static void platform_delay(uint32_t ms)

{

HAL_Delay(ms);

}

 

static void tx_com(uint8_t *tx_buffer, uint16_t len)

{

HAL_UART_Transmit(&huart2, tx_buffer, len, 1000);

}

static uint32_t npy_halfbits_to_floatbits(uint16_t h)

{

uint16_t h_exp, h_sig;

uint32_t f_sgn, f_exp, f_sig;

 

h_exp = (h&0x7c00u);

f_sgn = ((uint32_t)h&0x8000u) << 16;

switch (h_exp) {

case 0x0000u: /* 0 or subnormal */

h_sig = (h&0x03ffu);

/* Signed zero */

if (h_sig == 0) {

return f_sgn;

}

/* Subnormal */

h_sig <<= 1;

while ((h_sig&0x0400u) == 0) {

h_sig <<= 1;

h_exp++;

}

f_exp = ((uint32_t)(127 - 15 - h_exp)) << 23;

f_sig = ((uint32_t)(h_sig&0x03ffu)) << 13;

return f_sgn + f_exp + f_sig;

case 0x7c00u: /* inf or NaN */

/* All-ones exponent and a copy of the significand */

return f_sgn + 0x7f800000u + (((uint32_t)(h&0x03ffu)) << 13);

default: /* normalized */

/* Just need to adjust the exponent and shift */

return f_sgn + (((uint32_t)(h&0x7fffu) + 0x1c000u) << 13);

}

}

 

static float_t npy_half_to_float(uint16_t h)

{

union { float_t ret; uint32_t retbits; } conv;

conv.retbits = npy_halfbits_to_floatbits(h);

return conv.ret;

}

 

static void sflp2q(float quat[4], uint16_t sflp[3])

{

float sumsq = 0;

 

quat[0] = npy_half_to_float(sflp[0]);

quat[1] = npy_half_to_float(sflp[1]);

quat[2] = npy_half_to_float(sflp[2]);

 

for (uint8_t i = 0; i < 3; i++)

sumsq += quat[i] * quat[i];

 

if (sumsq > 1.0f) {

float n = sqrtf(sumsq);

quat[0] /= n;

quat[1] /= n;

quat[2] /= n;

sumsq = 1.0f;

}

 

quat[3] = sqrtf(1.0f - sumsq);

}

 

/* USER CODE END 4 */

 

void Error_Handler(void)

{

__disable_irq();

while (1)

{

}

}

 

 

"

Denise SANFILIPPO
ST Employee

Hello @marti3110,

I did a quick check of your code: it is printing the output values to serial terminal so you should use a serial terminal to see the quaternions.

marti3110
Associate II

@Denise SANFILIPPO I managed to visualize the quaternions through the output terminal and saved the data.

The commans I used are:

"

fifo_sflp.game_rotation = 1;

fifo_sflp.gravity = 1;

fifo_sflp.gbias = 1;

lsm6dsv16x_fifo_sflp_batch_set(&dev_ctx, fifo_sflp);

lsm6dsv16x_fifo_mode_set(&dev_ctx, LSM6DSV16X_STREAM_MODE);

lsm6dsv16x_xl_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_30Hz); // ODR dell'accelerometro

lsm6dsv16x_gy_data_rate_set(&dev_ctx, LSM6DSV16X_ODR_AT_30Hz); // ODR del giroscopio

lsm6dsv16x_sflp_data_rate_set(&dev_ctx, LSM6DSV16X_SFLP_30Hz);

lsm6dsv16x_sflp_game_rotation_set(&dev_ctx, PROPERTY_ENABLE);

lsm6dsv16x_sflp_gbias_t gbias;

gbias.gbias_x = 0.0f;

gbias.gbias_y = 0.0f;

gbias.gbias_z = 0.0f;

lsm6dsv16x_sflp_game_gbias_set(&dev_ctx, &gbias);

while (1)

{

// Controlla lo stato del FIFO

lsm6dsv16x_fifo_status_get(&dev_ctx, &fifo_status);

 

if (fifo_status.fifo_th == 1) {

// Leggi i dati dal FIFO

uint16_t num_samples = fifo_status.fifo_level;

 

while (num_samples--) {

lsm6dsv16x_fifo_out_raw_t raw_data;

lsm6dsv16x_fifo_out_raw_get(&dev_ctx, &raw_data);

 

int16_t *axis;

float gravity_mg[3];

float gbias_mdps[3];

 

// Se i dati sono del Game Rotation Vector

if (raw_data.tag == LSM6DSV16X_SFLP_GAME_ROTATION_VECTOR_TAG) {

// Converti i dati grezzi in quaternioni

sflp2q(quaternion, (uint16_t*)&raw_data.data[0]);

 

// Invia i dati via UART

snprintf((char*)tx_buffer, sizeof(tx_buffer), "Game Rotation \tX: %2.3f\tY: %2.3f\tZ: %2.3f\tW: %2.3f\r\n", quaternion[0], quaternion[1], quaternion[2], quaternion[3]);

tx_com(tx_buffer, strlen((char const*)tx_buffer));

}

else if (raw_data.tag == LSM6DSV16X_SFLP_GYROSCOPE_BIAS_TAG)

{

axis = (int16_t *)&raw_data.data[0];

gbias_mdps[0] = lsm6dsv16x_from_fs125_to_mdps(axis[0]);

gbias_mdps[1] = lsm6dsv16x_from_fs125_to_mdps(axis[1]);

gbias_mdps[2] = lsm6dsv16x_from_fs125_to_mdps(axis[2]);

snprintf((char *)tx_buffer, sizeof(tx_buffer), "GBIAS [mdps]:%4.2f\t%4.2f\t%4.2f\r\n",

(double_t)gbias_mdps[0], (double_t)gbias_mdps[1], (double_t)gbias_mdps[2]);

tx_com(tx_buffer, strlen((char const *)tx_buffer));

}

else if (raw_data.tag == LSM6DSV16X_SFLP_GRAVITY_VECTOR_TAG)

{

axis = (int16_t *)&raw_data.data[0];

gravity_mg[0] = lsm6dsv16x_from_sflp_to_mg(axis[0]);

gravity_mg[1] = lsm6dsv16x_from_sflp_to_mg(axis[1]);

gravity_mg[2] = lsm6dsv16x_from_sflp_to_mg(axis[2]);

snprintf((char *)tx_buffer, sizeof(tx_buffer), "Gravity [mg]:%4.2f\t%4.2f\t%4.2f\r\n",

(double_t)gravity_mg[0], (double_t)gravity_mg[1], (double_t)gravity_mg[2]);

tx_com(tx_buffer, strlen((char const *)tx_buffer));

}

}

}

MX_MEMS_Process();

}"

The commands I used to get Euler angles from quaternions in Matlab are the following:

"q=quaternion(W_values,X_values,Y_values,Z_values);
angles=eulerd(q,'ZYX','frame');

roll_eulerd=angles(:,3);
pitch_eulerd=angles(:,2);
yaw_eulerd=angles(:,1);

yaw_eulerd = yaw_eulerd - yaw_eulerd(1);"

In the first acquisition I performed a roll:+90, pitch:+90, yaw:+90 rotation and in the second acquisition I performed a roll:-90, pitch:-90, yaw:-90 rotation and I get the attached graphs.  roll_pitch_yaw_90.jpeg

 

roll_pitch_yaw_-90.jpeg

 The pitch angle corresponds to the rotations made but the Roll angle varies even when it varies pitch angle and the Yaw angle instead always has a strange trend. 

I also encountered this problem when implementing the Motion FX 6x and Motion FX 9x libraries and getting quaternions as output.

Since for my project I need to obtain very precise angles that do not vary when there is rotation around a single axis, what can I do?

Stefan3
Associate II

Hi Denis

Thanks for the good explanation.
One question about the SFLP.

Can I only read the values via the FIFO or also direct from the ‘register’?

Denise SANFILIPPO
ST Employee

Hi @Stefan3

the SFLP data can be read only from FIFO.

Stefan3
Associate II

Hi Denis

Ok. That's good

And this few register are on page 0 or I have to change the page once?

 

Register bit config

Bit description

SFLP_GAME_EN

Enables SFLP

SFLP_GBIAS_FIFO_EN

Enables Gbias in FIFO mode

SFLP_GRAVITY_FIFO_EN

Enables gravity vector in FIFO mode

SFLP_GAME_FIFO_EN

Enables game rotation vector in FIFO mode

SFLP_GAME_ODR_[2:0]

Configures SFLP ODR

Denise SANFILIPPO
ST Employee

Hi @marti3110 ,

This is not a problem with the algorithm (neither SFLP nor MotionFX 6X / 9X), but with the representation of the output using Euler angles, which by nature suffer from the well-known ‘gimbal lock’ issue.

If you try SFLP with MEMS Studio which visualizes the outputs via a 3D model (rotated using the quaternion generated by the sensor), you will see that the algorithm works more than well and without jumps.

Denise SANFILIPPO
ST Employee

Hi @Stefan3,

The registers SFLP_ODR (addr: 5Eh), EMB_FUNC_FIFO_EN_A (addr: 44h), EMB_FUNC_EN_A (addr: 04h), which contains the bits you where showing in your table, are part of the Embedded functions registers (see Table 4, paragraph 2.1 of AN5763). 

For these registers no need to change/select a page, as it happens for Embedded advanced features pages (paragraph 2.2 of AN5763) for which you need to select the advanced features dedicated page through the register called PAGE_SEL (addr: 02h).

marti3110
Associate II

Hi @Denise SANFILIPPO ,thanks for the explanation.

Stefan3
Associate II

Hi Denis

I have set the all the registers
It looks like it works.

I can read the FIFO values 

The values change but not yet properly. I just read the the six FIFO_data_out byte

I have not yet understood what this table means, 

2024-10-25_15-42-24.PNG

 

Denise SANFILIPPO
ST Employee

Hi @Stefan3 ,

Here is a short explanation about the FIFO and SFLP block inside LSM6DSV16X:

The FIFO can be configured to store the SFLP game rotation vector, gravity vector, gyroscope bias.

The reconstruction of a FIFO stream is a simple task thanks to the TAG_SENSOR_[4:0] field of the FIFO_DATA_OUT_TAG register that allows recognizing the meaning of a word in FIFO.

For SFLP, the table below contains the values for the TAG_SENSOR_[4:0] field:

TAG_SENSOR_[4:0] Sensor name Sensor name Description
0x13 Game rotation vector Virtual SFLP-generated game rotation vector
0x16 Gyroscope bias Virtual SFLP-generated gyroscope bias
0x17 Gravity vector Virtual SFLP-generated gravity vector

Data can be retrieved from the FIFO through six dedicated registers, from address 79h to 7Eh, in particular: 

• FIFO_DATA_OUT_X[15:0] = FIFO_DATA_OUT_X_L + FIFO_DATA_OUT_X_H << 8
• FIFO_DATA_OUT_Y[15:0] = FIFO_DATA_OUT_Y_L + FIFO_DATA_OUT_Y_H << 8
• FIFO_DATA_OUT_Z[15:0] = FIFO_DATA_OUT_Z_L + FIFO_DATA_OUT_Z_H << 8

A dedicated sensor fusion block (SFLP) is available for generating the following virtual sensors based on
processing the accelerometer and gyroscope data:
• Game rotation vector, which provides a quaternion representing the attitude of the device
• Gravity vector, which provides a three dimensional vector representing the direction of gravity
• Gyroscope bias, which provides a three dimensional vector representing the gyroscope bias

SFLP-generated sensors are read only from FIFO and they are selectively enabled:
• Game rotation vector is batched by setting the SFLP_GAME_FIFO_EN bit of the EMB_FUNC_FIFO_EN_A
register to 1.
• Gravity vector is batched by setting the SFLP_GRAVITY_FIFO_EN bit of EMB_FUNC_FIFO_EN_A
register to 1.
• Gyroscope bias is batched by setting the SFLP_GBIAS_FIFO_EN bit of the EMB_FUNC_FIFO_EN_A
register to 1.

If batching in FIFO is enabled, the SFLP-generated sensors are stored in FIFO according to the SFLP output data rate.

The format for the SFLP-generated sensors in FIFO is listed below:
• Game rotation vector: X, Y, and Z axes (vector part of the quaternion) are stored in half-precision floating point format, where w (scalar part of the quaternion) is computed in software after reading the data from
the FIFO, since the game rotation vector is a unit quaternion.
• Gravity vector: X, Y, and Z axes are stored as 16-bit two's complement number with ±2 g sensitivity.
• Gyroscope bias: X, Y, and Z axes are stored as 16-bit two's complement number with ±125 dps sensitivity.

I suggest looking at the following example lsm6dsv16x_sensor_fusion available inside our official GitHub: it can be useful to understand the various steps needed to use SFLP with LSM6DSV16X. 

Please refer also to AN5763 for further info. 

Stefan3
Associate II

Hi Denis

I have a very nice signal from the Accellero and from Gyroscope.

From the SFLP I get some problems.

2024-10-29_12-06-49.PNG

2024-10-29_12-04-07.PNG

2024-10-29_12-10-23.PNG

Denise SANFILIPPO
ST Employee

Hi @Stefan3 ,

can you provide more details about your plots? 
In particular, how are you reading SFLP data and which is the unit of measurement of your plots?

Stefan3
Associate II

Hi Denis

I read with an Interrupt 30Hz the six register and display the read value 1000 times in Chart. I do not move the sensor. For the first test I read only Gyro data, for the second only Acceloero data and for the third test only the fusion data.
If I move the sensor all values change, that would be ok so far

 

2024-10-29_13-43-37.PNG

Denise SANFILIPPO
ST Employee

Hi @Stefan3 ,

Are you converting the SFLP output data from half-precision floating-point format (float16) to float format?

You should interpret SFLP data as follows:

image (5).png

Please note that the SFLP output data is quaternions, not angles. 

Stefan3
Associate II

Hi Denis

I guess I still have to read FIFO_DATA_OUT_TAG (78h) and wait until the value there is 0x13, 0x16 or 0x17.

Or how do I know which data to read?

 

FIFO_DATA_OUT_TAG (78h)

Denise SANFILIPPO
ST Employee

Hi @Stefan3,

yes, by reading the FIFO_DATA_OUT_TAG register, it is possible to understand to which sensor the data of the current reading belongs and to check if data are consistent. In fact, the reconstruction of a FIFO stream is a simple task thanks to the TAG_SENSOR field of the FIFO_DATA_OUT_TAG register that allows recognizing the meaning of a word in FIFO.

Stefan3
Associate II

Hi Denis

With the attention of the TAG I can now read SFLP.
But in the data I regularly had wrong values.

For example 20,21,24. in all the 3Channel

Every time I got these values, I read the data again and then they were ok again.

Now I am trying to convert the values into roll, pitch, yaw

 

2024-10-30_16-15-01.PNG

Stefan3
Associate II

Hi Denis

I have to read the game vectors and send them from the board to the computer.

In the computer I can convert the half precision floating to Float32.

The values I get are from about -0.9 to +0.9.

Now I have to convert these values into yaw, roll, pitch.
These values should be 0-360 degrees (or +-180 degrees).
How do I do this? I can't just multiply by 180

 

Denise SANFILIPPO
ST Employee

Hi @Stefan3,

Here below you can find an example based on quaternions read by the IMU. The IMU is oriented according to ENU frame. 

The formula below can be used to convert the quaternions to Euler angles.

 

void quat_2_euler(float *euler, float *quat)

{

  float sx = quat[1];

  float sy = quat[2];

  float sz = quat[0];

  float sw = quat[3];

 

  if (sw < 0.0f) {

    sx *= -1.0f;

    sy *= -1.0f;

    sz *= -1.0f;

    sw *= -1.0f;

  }

 

  float sqx = sx * sx;

  float sqy = sy * sy;

  float sqz = sz * sz;

 

  euler[0] = -atan2f(2.0f * (sy * sw + sx * sz), 1.0f - 2.0f * (sqy + sqx));

  euler[1] = -atan2f(2.0f * (sx * sy + sz * sw), 1.0f - 2.0f * (sqx + sqz));

  euler[2] = -asinf(2.0f * (sx * sw - sy * sz));

 

  if (euler[0] < 0.0f)

    euler[0] += 2.0f * M_PI;

 

  for (uint8_t i = 0; i < 3; i++) {

    euler[i] = RAD2DEG(euler[i]);

  }

}

 

If you have further doubts, you can also ask them inside the Product Forum of MEMS sensors

eliana
Associate

The LSM6DSV16X’s SFLP algorithm simplifies sensor fusion by offering pre-calibrated outputs for device orientation, gravity, and gyroscope bias. To calculate fusion data, use rotation matrices based on yaw, pitch, and roll angles.

The Unico-GUI tool can further assist with setup and testing. For hands-on demos, use the STEVAL-MKI109V3 board and configure SFLP registers to enable features like gravity vectors and game rotation. This setup is excellent for developing efficient applications quickly.

Stefan3
Associate II

Hi Denis, Hi Eliane

I read the game rotation vectors from the LSM9
But unfortunately no fourth value. This is called the scalar part. float sw = quat[3];

How do I calculate this scalar part or how do I read it?

  float sx = quat[1];

  float sy = quat[2];

  float sz = quat[0];

 float sw = quat[3];

 

 

2024-11-13_09-41-41.PNG

 

Denise SANFILIPPO
ST Employee

Hi @Stefan3 ,

as AN5763 states, X, Y, and Z axes (vector part of the quaternion) of the game rotation vector are stored in half-precision floating-point format, while w (scalar part of the quaternion) is computed in software after reading the data from the FIFO, since the game rotation vector is a unit quaternion.

You can refer to our GitHub to retrieve the scalar part w of the quaternion, starting from X, Y, Z of the game rotation vector. Focus on the sflp2q function:

 static void sflp2q(float quat[4], uint16_t sflp[3])
{
     float sumsq = 0;

     quat[0] = npy_half_to_float(sflp[0]);
     quat[1] = npy_half_to_float(sflp[1]);
     quat[2] = npy_half_to_float(sflp[2]);

     for (uint8_t i = 0; i < 3; i++)
     sumsq += quat[i] * quat[i];

     if (sumsq > 1.0f) {
     float n = sqrtf(sumsq);
     quat[0] /= n;
     quat[1] /= n;
     quat[2] /= n;
     sumsq = 1.0f;
}

     quat[3] = sqrtf(1.0f - sumsq);
}


 

Stefan3
Associate II

Hi Denis

It now works from configuring the LSM6, reading from FIFO, sending to USB and receiving in the App.

Also the conversion from FP16 to FB32 and the calculation of pitch, yaw, roll and each in grad degres is perfect.

A small problem is still, regular spikes in the data.

In decimal the values are about 20-40 und if I convert to FP32 it's looks like a very small number or somtimes very high number

I suppose I read a counter or something similar instead of the quaternion.

2024-11-15_18-05-29.PNG

 

 

2024-11-15_17-52-29.PNG

Denise SANFILIPPO
ST Employee

Hi @Stefan3 ,

In general, you must read from the FIFO (e.g., a set of 7 bytes) and interpret the contents of the data section according to the tag, as you did. 
I suggest looking at the following example lsm6dsv16x_sensor_fusion available inside our official GitHub: it can be useful to understand the various steps needed to use SFLP with LSM6DSV16X. 

Is the plotted data the data which has been converted to float?
Or do you multiply the data by a scaling factor? Data should be in the range [-1;1].

Stefan3
Associate II

Hi Denis

Ok before I had only read 1 byte and if that was correct then I have read the 6 others.

With your suggestion to read 7 at once and then when the tag is right I use it, it works very well and also faster.

I can read and transmit at 15Hz, 30Hz, 60Hz, 120Hz, 240Hz, 480Hz without any problems.

The old chart shows Pitch, Yaw, Roll in Grad degrees.

Here a new chart with both, the convertet Float32 value (top) and the chart with the Pitch, Yaw, Roll (bottom). The x (pitch) is 358 grad. It changes the value when I turn, but I suspect not in the right range

 

2024-11-21_10-56-14.PNG

Denise SANFILIPPO
ST Employee

Hi @Stefan3 

a possible error in your code could be an incorrect convention used inside the function called “quat_2_euler”.

My suggestion is to convert the quaternion in such a way it is in the format [x, y, z, w], then you should pass it to the function void quat_2_euler(float *euler, float *quat) without applying any swap of the axes.

So the input of the quat_2_euler function must be quaternion (quat) in the format [x, y, z, w], while the output are the Euler angles in the format [yaw, pitch, roll] in deg. The convention of the Euler angles are that for Android.

Version history
Last update:
‎2023-11-20 06:55 AM
Updated by: