on
2023-08-28
07:30 AM
- edited on
2023-11-20
06:55 AM
by
Laurids_PETERSE
Rotation matrices can be obtained from these three examples using matrix multiplication. For example, the product:
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:
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:
Sensor fusion performance and time required to reach steady state
The hardware setup is STEVAL-MKI109V3 and DIL24 adapter board STEVAL-MKI227KA.
The software used is Unico-GUI.
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.
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.
Coordinates:
Pitch rotation around the X-axisΩP
Roll rotation around the Y-axis ΩR
Heading/Yaw rotation around the Z-axisΩY
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.
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
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
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.
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)
{
}
}
"
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.
@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.
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?
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’?
Hi @Stefan3,
the SFLP data can be read only from FIFO.
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 |
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.
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).
Hi @Denise SANFILIPPO ,thanks for the explanation.
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,
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.
Hi Denis
I have a very nice signal from the Accellero and from Gyroscope.
From the SFLP I get some problems.
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?
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
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:
Please note that the SFLP output data is quaternions, not angles.
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)
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.
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
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
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.
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.
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];
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);
}
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.
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].
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
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.
Hi Denis
In your code, the axes are assigned as follows in the picture.
I have now done the same for me.
And I use this:
yaw=euler[0]
pitch=euler[1]
roll=euler[2]
Then the results look different. I will do further tests and see how it goes.
One more question: Does the sensitivity of accelero and gyro have an influence?