From 7ae03089d2eb959c4d94cf5224b33cde459328b7 Mon Sep 17 00:00:00 2001
From: Fabrizio Monti <fabrizio.monti@skywarder.eu>
Date: Tue, 1 Aug 2023 09:15:14 +0000
Subject: [PATCH] [LSM6DSRX] Added driver for the LSM6DSRX 6 axis IMU

---
 CMakeLists.txt                               |   3 +
 cmake/boardcore.cmake                        |   1 +
 src/shared/sensors/LSM6DSRX/LSM6DSRX.cpp     | 924 +++++++++++++++++++
 src/shared/sensors/LSM6DSRX/LSM6DSRX.h       | 242 +++++
 src/shared/sensors/LSM6DSRX/LSM6DSRXConfig.h | 210 +++++
 src/shared/sensors/LSM6DSRX/LSM6DSRXData.h   |  63 ++
 src/shared/sensors/LSM6DSRX/LSM6DSRXDefs.h   | 188 ++++
 src/tests/sensors/test-lsm6dsrx.cpp          | 310 +++++++
 8 files changed, 1941 insertions(+)
 create mode 100644 src/shared/sensors/LSM6DSRX/LSM6DSRX.cpp
 create mode 100644 src/shared/sensors/LSM6DSRX/LSM6DSRX.h
 create mode 100644 src/shared/sensors/LSM6DSRX/LSM6DSRXConfig.h
 create mode 100644 src/shared/sensors/LSM6DSRX/LSM6DSRXData.h
 create mode 100644 src/shared/sensors/LSM6DSRX/LSM6DSRXDefs.h
 create mode 100644 src/tests/sensors/test-lsm6dsrx.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 5352d725d..fd08e56e3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -425,6 +425,9 @@ sbs_target(test-lps28dfw stm32f767zi_nucleo)
 add_executable(test-lps22df src/tests/sensors/test-lps22df.cpp)
 sbs_target(test-lps22df stm32f767zi_nucleo)
 
+add_executable(test-lsm6dsrx src/tests/sensors/test-lsm6dsrx.cpp)
+sbs_target(test-lsm6dsrx stm32f407vg_stm32f4discovery)
+
 #-----------------------------------------------------------------------------#
 #                                Tests - Utils                                #
 #-----------------------------------------------------------------------------#
diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake
index 3e29187ee..435e99a41 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -111,6 +111,7 @@ foreach(OPT_BOARD ${BOARDS})
         ${SBS_BASE}/src/shared/sensors/LIS2MDL/LIS2MDL.cpp
         ${SBS_BASE}/src/shared/sensors/LPS28DFW/LPS28DFW.cpp
         ${SBS_BASE}/src/shared/sensors/LPS22DF/LPS22DF.cpp
+        ${SBS_BASE}/src/shared/sensors/LSM6DSRX/LSM6DSRX.cpp
 
         # Calibration
         ${SBS_BASE}/src/shared/sensors/calibration/BiasCalibration/BiasCalibration.cpp
diff --git a/src/shared/sensors/LSM6DSRX/LSM6DSRX.cpp b/src/shared/sensors/LSM6DSRX/LSM6DSRX.cpp
new file mode 100644
index 000000000..3294db578
--- /dev/null
+++ b/src/shared/sensors/LSM6DSRX/LSM6DSRX.cpp
@@ -0,0 +1,924 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "LSM6DSRX.h"
+
+#include <assert.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <utils/Constants.h>
+#include <utils/Debug.h>
+
+#include <cmath>
+
+namespace Boardcore
+{
+
+LSM6DSRX::LSM6DSRX(SPIBus& bus, miosix::GpioPin csPin,
+                   SPIBusConfig busConfiguration, LSM6DSRXConfig& configuration)
+    : spiSlave(bus, csPin, busConfiguration), config(configuration)
+{
+    isInit = false;
+
+    // check that the watermark value is suitable
+    config.fifoWatermark = std::min(config.fifoWatermark, uint16_t(170));
+
+    /**
+     * watermark value is multiplied by 3, because every sample is composed of 3
+     * data: timestamp, accelerometer data, gyroscope data. The sensor's fifo
+     * contains all these data one by one. These data are then collected and
+     * crafted inside a single sample by the driver.
+     */
+    config.fifoWatermark *= 3;
+
+    // check that ACC_ODR is set to HZ_1_6 only if OPERATING_MODE is equal to
+    // NORMAL
+    if (config.odrAcc == LSM6DSRXConfig::ACC_ODR::HZ_1_6)
+    {
+        config.opModeAcc = LSM6DSRXConfig::OPERATING_MODE::NORMAL;
+    }
+}
+
+bool LSM6DSRX::init()
+{
+    if (isInit)
+    {
+        LOG_ERR(logger, "init() should be called once");
+        lastError = SensorErrors::ALREADY_INIT;
+        return false;
+    }
+
+    if (checkWhoAmI() == false)
+    {
+        LOG_ERR(logger, "Got bad CHIPID");
+        lastError = SensorErrors::INVALID_WHOAMI;
+        return false;
+    }
+
+    // Set BDU and multiple spi read/write
+    {
+        SPITransaction spiTransaction{spiSlave};
+
+        uint8_t value = static_cast<uint8_t>(config.bdu) << 6;  // set bdu
+        value |= 1 << 2;  // set multiple spi read/write
+
+        spiTransaction.writeRegister(LSM6DSRXDefs::REG_CTRL3_C, value);
+    }
+
+    // Setup accelerometer
+    initAccelerometer();
+
+    // Setup gyroscope
+    initGyroscope();
+
+    // setup Fifo
+    initFifo();
+
+    // enable timestamp
+    {
+        constexpr uint8_t REG_CTRL10_TIMESTAMP_EN = 1 << 5;
+
+        SPITransaction spiTransaction{spiSlave};
+        spiTransaction.writeRegister(LSM6DSRXDefs::REG_CTRL10_C,
+                                     REG_CTRL10_TIMESTAMP_EN);
+    }
+
+    // set interrupt
+    initInterrupts();
+
+    // set timestamps
+    correlateTimestamps();
+    sensorTimestampResolution =
+        getSensorTimestampResolution() *
+        1000;  // return value is in milliseconds, need microseconds.
+
+    isInit    = true;
+    lastError = SensorErrors::NO_ERRORS;
+    return true;
+}
+
+void LSM6DSRX::initAccelerometer()
+{
+    uint8_t configByte = 0;
+    SPITransaction spiTransaction{spiSlave};
+
+    // Setup accelerometer
+
+    // set accelerometer odr, fullscale and high resolution
+    configByte = static_cast<uint8_t>(config.odrAcc) << 4 |  // odr
+                 static_cast<uint8_t>(config.fsAcc) << 2 |   // fullscale
+                 0 << 1;  // high resolution selection
+    spiTransaction.writeRegister(LSM6DSRXDefs::REG_CTRL1_XL, configByte);
+
+    // set accelerometer performance mode
+    configByte = static_cast<uint8_t>(config.opModeAcc) << 4;
+    spiTransaction.writeRegister(LSM6DSRXDefs::REG_CTRL6_C, configByte);
+
+    // set sensitivity
+    switch (config.fsAcc)
+    {
+        case LSM6DSRXConfig::ACC_FULLSCALE::G2:
+            sensitivityAcc = 0.061;
+            break;
+        case LSM6DSRXConfig::ACC_FULLSCALE::G4:
+            sensitivityAcc = 0.122;
+            break;
+        case LSM6DSRXConfig::ACC_FULLSCALE::G8:
+            sensitivityAcc = 0.244;
+            break;
+        case LSM6DSRXConfig::ACC_FULLSCALE::G16:
+            sensitivityAcc = 0.488;
+            break;
+        default:
+            config.fsAcc   = LSM6DSRXConfig::ACC_FULLSCALE::G2;
+            sensitivityAcc = 0.061;
+            break;
+    };
+
+    // the sensor's unit of measurement is milli-g, we need to convert
+    // to meters per second squared
+    constexpr float accelerationConversion = Constants::g / 1000.0;
+    sensitivityAcc *= accelerationConversion;
+}
+
+void LSM6DSRX::initGyroscope()
+{
+    uint8_t configByte = 0;
+    SPITransaction spiTransaction{spiSlave};
+
+    // set odr and fullscale
+    configByte = static_cast<uint8_t>(config.odrGyr) << 4 |  // odr
+                 static_cast<uint8_t>(config.fsGyr);         // fullscale
+    spiTransaction.writeRegister(LSM6DSRXDefs::REG_CTRL2_G, configByte);
+
+    // set performance mode
+    configByte = static_cast<uint8_t>(config.opModeGyr) << 7;
+    spiTransaction.writeRegister(LSM6DSRXDefs::REG_CTRL7_G, configByte);
+
+    // set sensitivity
+    switch (config.fsGyr)
+    {
+        case LSM6DSRXConfig::GYR_FULLSCALE::DPS_125:
+            sensitivityGyr = 4.375;
+            break;
+        case LSM6DSRXConfig::GYR_FULLSCALE::DPS_250:
+            sensitivityGyr = 8.75;
+            break;
+        case LSM6DSRXConfig::GYR_FULLSCALE::DPS_500:
+            sensitivityGyr = 17.5;
+            break;
+        case LSM6DSRXConfig::GYR_FULLSCALE::DPS_1000:
+            sensitivityGyr = 35.0;
+            break;
+        case LSM6DSRXConfig::GYR_FULLSCALE::DPS_2000:
+            sensitivityGyr = 70.0;
+            break;
+        case LSM6DSRXConfig::GYR_FULLSCALE::DPS_4000:
+            sensitivityGyr = 140.0;
+            break;
+        default:
+            config.fsGyr   = LSM6DSRXConfig::GYR_FULLSCALE::DPS_125;
+            sensitivityGyr = 4.375;
+            break;
+    }
+
+    // the sensor's unit of measurement is milli-degree per second, we need
+    // to convert to radians per second
+    constexpr float angularConversion = Constants::DEGREES_TO_RADIANS / 1000.0;
+    sensitivityGyr *= angularConversion;
+}
+
+void LSM6DSRX::initFifo()
+{
+    // setup Fifo
+    uint8_t configByte = 0;
+    SPITransaction spiTransaction{spiSlave};
+
+    // select batch data rate in FIFO_CTRL3
+    configByte = static_cast<uint8_t>(config.odrAcc) |  // accelerometer bdr
+                 (static_cast<uint8_t>(config.odrGyr) << 4);  // gyroscope bdr
+    spiTransaction.writeRegister(LSM6DSRXDefs::REG_FIFO_CTRL3, configByte);
+
+    // set fifo mode, batch data rate for temperature sensor and the decimation
+    // factor for timestamp batching
+    configByte = static_cast<uint8_t>(config.fifoMode) |  // fifo operating mode
+                 static_cast<uint8_t>(config.fifoTemperatureBdr)
+                     << 4 |  // batch data rate for temperature data
+                 static_cast<uint8_t>(config.fifoTimestampDecimation)
+                     << 6;  // timestamp decimation
+    spiTransaction.writeRegister(LSM6DSRXDefs::REG_FIFO_CTRL4, configByte);
+}
+
+void LSM6DSRX::initInterrupts()
+{
+    uint8_t buf[] = {0, 0};
+    SPITransaction spi{spiSlave};
+
+    buf[0] = static_cast<uint8_t>(
+        config.int1InterruptSelection);  // set interrupt on pin INT1
+    buf[1] = static_cast<uint8_t>(
+        config.int2InterruptSelection);  // set interrupt on pin INT2
+    spi.writeRegisters(LSM6DSRXDefs::REG_INT1_CTRL, buf, 2);
+
+    // set watermark level
+    buf[0] = static_cast<uint8_t>(config.fifoWatermark &
+                                  255);  // the first 8bits of the number.
+    buf[1] = static_cast<uint8_t>((config.fifoWatermark >> 8) &
+                                  0x01);  // the 9th bit of the number.
+    spi.writeRegisters(LSM6DSRXDefs::REG_FIFO_CTRL1, buf, 2);
+}
+
+bool LSM6DSRX::selfTestAcc()
+{
+    bool returnValue        = false;
+    uint8_t byteValue       = 0;  // used to read and write in registers
+    uint8_t idx             = 0;
+    const uint8_t SIZE_DATA = 5;  // number of sample for the test
+    SPITransaction spi{spiSlave};
+
+    // sleep time for data ready interrupt (150% odr during self-test)
+    // expressed in milliseconds.
+    // for this self-test the odr is 52Hz
+    const unsigned int dataReadyWaitTime = 29;
+
+    LSM6DSRXData averageSF;      // average data during self test
+    LSM6DSRXData averageNormal;  // average normal data
+
+    // set registers
+
+    // set odr=52Hz, fs=2g
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL1_XL, 0x30);
+
+    // power off the gyro
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL2_G, 0x00);
+
+    // set bdu to UPDATE_AFTER_READ
+    // register address automatically incremented during a multiple byte access
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL3_C, 0x44);
+
+    // disable gyro sleep mode
+    // disable data ready
+    // disable i2c
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL4_C, 0x00);
+
+    // disable accelerometer and gyro self test (for acc it will be enabled
+    // later on)
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL5_C, 0x00);
+
+    // more settings for the accelerometer (performance mode)
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL6_C, 0x00);
+
+    // gyro settings
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL7_G, 0x00);
+
+    // disables accelerometers additional filters
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL8_XL, 0x00);
+
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL9_XL, 0x00);
+
+    // disable timestamp counter
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL10_C, 0x00);
+
+    // wait for stable output
+    miosix::Thread::sleep(100);
+
+    // wait for accelerometer data ready
+    byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+    byteValue = byteValue & 0x01;
+    while (byteValue != 1)
+    {
+        miosix::Thread::sleep(dataReadyWaitTime);
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = byteValue & 0x01;
+    }
+
+    // read and discard data
+    getAxisData(LSM6DSRXDefs::REG_OUTX_L_A, LSM6DSRXDefs::REG_OUTX_H_A);
+    getAxisData(LSM6DSRXDefs::REG_OUTY_L_A, LSM6DSRXDefs::REG_OUTY_H_A);
+    getAxisData(LSM6DSRXDefs::REG_OUTZ_L_A, LSM6DSRXDefs::REG_OUTZ_H_A);
+
+    // read normal data (self test disabled)
+    for (idx = 0; idx < SIZE_DATA; ++idx)
+    {
+        // wait for accelerometer data ready
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = byteValue & 0x01;
+        while (byteValue != 1)
+        {
+            miosix::Thread::sleep(dataReadyWaitTime);
+            byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+            byteValue = byteValue & 0x01;
+        }
+
+        // read data
+        averageNormal.accelerationX += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTX_L_A, LSM6DSRXDefs::REG_OUTX_H_A, 0.061));
+        averageNormal.accelerationY += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTY_L_A, LSM6DSRXDefs::REG_OUTY_H_A, 0.061));
+        averageNormal.accelerationZ += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTZ_L_A, LSM6DSRXDefs::REG_OUTZ_H_A, 0.061));
+    }
+    averageNormal.accelerationX /= SIZE_DATA;
+    averageNormal.accelerationY /= SIZE_DATA;
+    averageNormal.accelerationZ /= SIZE_DATA;
+
+    // enable accelerometer positive sign self test
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL5_C, 0x01);
+
+    // wait for stable output
+    miosix::Thread::sleep(100);
+
+    // wait for accelerometer data ready
+    byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+    byteValue = byteValue & 0x01;
+    while (byteValue != 1)
+    {
+        miosix::Thread::sleep(dataReadyWaitTime);
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = byteValue & 0x01;
+    }
+
+    // read and discard data
+    getAxisData(LSM6DSRXDefs::REG_OUTX_L_A, LSM6DSRXDefs::REG_OUTX_H_A);
+    getAxisData(LSM6DSRXDefs::REG_OUTY_L_A, LSM6DSRXDefs::REG_OUTY_H_A);
+    getAxisData(LSM6DSRXDefs::REG_OUTZ_L_A, LSM6DSRXDefs::REG_OUTZ_H_A);
+
+    // read self test data
+    for (idx = 0; idx < SIZE_DATA; ++idx)
+    {
+        // wait for accelerometer data ready
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = byteValue & 0x01;
+        while (byteValue != 1)
+        {
+            miosix::Thread::sleep(dataReadyWaitTime);
+            byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+            byteValue = byteValue & 0x01;
+        }
+
+        // read data
+        averageSF.accelerationX += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTX_L_A, LSM6DSRXDefs::REG_OUTX_H_A, 0.061));
+        averageSF.accelerationY += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTY_L_A, LSM6DSRXDefs::REG_OUTY_H_A, 0.061));
+        averageSF.accelerationZ += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTZ_L_A, LSM6DSRXDefs::REG_OUTZ_H_A, 0.061));
+    }
+    averageSF.accelerationX /= SIZE_DATA;
+    averageSF.accelerationY /= SIZE_DATA;
+    averageSF.accelerationZ /= SIZE_DATA;
+
+    if (LSM6DSRXDefs::ACC_SELF_TEST_MIN <=
+            std::abs(averageSF.accelerationX - averageNormal.accelerationX) &&
+        std::abs(averageSF.accelerationX - averageNormal.accelerationX) <=
+            LSM6DSRXDefs::ACC_SELF_TEST_MAX &&
+        LSM6DSRXDefs::ACC_SELF_TEST_MIN <=
+            std::abs(averageSF.accelerationY - averageNormal.accelerationY) &&
+        std::abs(averageSF.accelerationY - averageNormal.accelerationY) <=
+            LSM6DSRXDefs::ACC_SELF_TEST_MAX &&
+        LSM6DSRXDefs::ACC_SELF_TEST_MIN <=
+            std::abs(averageSF.accelerationZ - averageNormal.accelerationZ) &&
+        std::abs(averageSF.accelerationZ - averageNormal.accelerationZ) <=
+            LSM6DSRXDefs::ACC_SELF_TEST_MAX)
+    {
+        returnValue = true;
+    }
+    else
+    {
+        LOG_ERR(logger, "Accelerometer self-test failed!");
+        returnValue = false;
+    }
+
+    // Disable self-test
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL5_C, 0x00);
+    // Disable sensor
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL1_XL, 0x00);
+
+    return returnValue;
+}
+
+bool LSM6DSRX::selfTestGyr()
+{
+    bool returnValue        = true;
+    uint8_t byteValue       = 0;
+    uint8_t idx             = 0;
+    const uint8_t SIZE_DATA = 5;
+    SPITransaction spi{spiSlave};
+
+    // sleep time for data ready interrupt (150% odr during self-test)
+    // expressed in milliseconds.
+    // for this self-test the odr is 208Hz
+    const unsigned int dataReadyWaitTime = 8;
+
+    LSM6DSRXData averageSF;      // average data during self test
+    LSM6DSRXData averageNormal;  // average normal data
+
+    // set registers
+
+    // disable accelerometer (power down)
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL1_XL, 0x00);
+
+    // set odr and fullscale for the gyro
+    // odr: 208Hz
+    // fs: 2000 dps
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL2_G, 0x5C);
+
+    // set bdu to UPDATE_AFTER_READ
+    // register address automatically incremented during a multiple byte access
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL3_C, 0x44);
+
+    // disable gyro sleep mode
+    // disable data ready
+    // disable i2c
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL4_C, 0x00);
+
+    // disable accelerometer and gyro self test
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL5_C, 0x00);
+
+    // more settings for the accelerometer (performance mode)
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL6_C, 0x00);
+
+    // enables high performance mode for the gyro
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL7_G, 0x00);
+
+    // disables accelerometer's filters
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL8_XL, 0x00);
+
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL9_XL, 0x00);
+
+    // disable timestamp counter
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL10_C, 0x00);
+
+    // sleep for stable output
+    miosix::Thread::sleep(100);
+
+    // wait for gyroscope data ready
+    byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+    byteValue = (byteValue & 0x02) >> 1;
+    while (byteValue != 1)
+    {
+        miosix::Thread::sleep(dataReadyWaitTime);
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = (byteValue & 0x02) >> 1;
+    }
+    // read and discard data
+    getAxisData(LSM6DSRXDefs::REG_OUTX_L_G, LSM6DSRXDefs::REG_OUTX_H_G);
+    getAxisData(LSM6DSRXDefs::REG_OUTY_L_G, LSM6DSRXDefs::REG_OUTY_H_G);
+    getAxisData(LSM6DSRXDefs::REG_OUTZ_L_G, LSM6DSRXDefs::REG_OUTZ_H_G);
+
+    // read normal data (self test disabled)
+    for (idx = 0; idx < SIZE_DATA; ++idx)
+    {
+        // wait for gyroscope data ready
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = (byteValue & 0x02) >> 1;
+        while (byteValue != 1)
+        {
+            miosix::Thread::sleep(dataReadyWaitTime);
+            byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+            byteValue = (byteValue & 0x02) >> 1;
+        }
+
+        // read data
+        averageNormal.angularSpeedX += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTX_L_G, LSM6DSRXDefs::REG_OUTX_H_G, 0.070));
+        averageNormal.angularSpeedY += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTY_L_G, LSM6DSRXDefs::REG_OUTY_H_G, 0.070));
+        averageNormal.angularSpeedZ += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTZ_L_G, LSM6DSRXDefs::REG_OUTZ_H_G, 0.070));
+    }
+    averageNormal.angularSpeedX /= SIZE_DATA;
+    averageNormal.angularSpeedY /= SIZE_DATA;
+    averageNormal.angularSpeedZ /= SIZE_DATA;
+
+    // enable gyroscope positive sign self test
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL5_C, 0x04);
+
+    // wait for stable output
+    miosix::Thread::sleep(100);
+
+    // wait for gyroscope data ready
+    byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+    byteValue = (byteValue & 0x02) >> 1;
+    while (byteValue != 1)
+    {
+        miosix::Thread::sleep(dataReadyWaitTime);
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = (byteValue & 0x02) >> 1;
+    }
+    // read and discard data
+    getAxisData(LSM6DSRXDefs::REG_OUTX_L_G, LSM6DSRXDefs::REG_OUTX_H_G);
+    getAxisData(LSM6DSRXDefs::REG_OUTY_L_G, LSM6DSRXDefs::REG_OUTY_H_G);
+    getAxisData(LSM6DSRXDefs::REG_OUTZ_L_G, LSM6DSRXDefs::REG_OUTZ_H_G);
+
+    // read self test data
+    for (idx = 0; idx < SIZE_DATA; ++idx)
+    {
+        // wait for gyroscope data ready
+        byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+        byteValue = (byteValue & 0x02) >> 1;
+        while (byteValue != 1)
+        {
+            miosix::Thread::sleep(dataReadyWaitTime);
+            byteValue = spi.readRegister(LSM6DSRXDefs::REG_STATUS);
+            byteValue = (byteValue & 0x02) >> 1;
+        }
+
+        // read data
+        averageSF.angularSpeedX += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTX_L_G, LSM6DSRXDefs::REG_OUTX_H_G, 0.070));
+        averageSF.angularSpeedY += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTY_L_G, LSM6DSRXDefs::REG_OUTY_H_G, 0.070));
+        averageSF.angularSpeedZ += static_cast<float>(getAxisData(
+            LSM6DSRXDefs::REG_OUTZ_L_G, LSM6DSRXDefs::REG_OUTZ_H_G, 0.070));
+    }
+    averageSF.angularSpeedX /= SIZE_DATA;
+    averageSF.angularSpeedY /= SIZE_DATA;
+    averageSF.angularSpeedZ /= SIZE_DATA;
+
+    if (LSM6DSRXDefs::GYR_SELF_TEST_MIN <=
+            std::abs(averageSF.angularSpeedX - averageNormal.angularSpeedX) &&
+        std::abs(averageSF.angularSpeedX - averageNormal.angularSpeedX) <=
+            LSM6DSRXDefs::GYR_SELF_TEST_MAX &&
+        LSM6DSRXDefs::GYR_SELF_TEST_MIN <=
+            std::abs(averageSF.angularSpeedY - averageNormal.angularSpeedY) &&
+        std::abs(averageSF.angularSpeedY - averageNormal.angularSpeedY) <=
+            LSM6DSRXDefs::GYR_SELF_TEST_MAX &&
+        LSM6DSRXDefs::GYR_SELF_TEST_MIN <=
+            std::abs(averageSF.angularSpeedZ - averageNormal.angularSpeedZ) &&
+        std::abs(averageSF.angularSpeedZ - averageNormal.angularSpeedZ) <=
+            LSM6DSRXDefs::GYR_SELF_TEST_MAX)
+    {
+        returnValue = true;
+    }
+    else
+    {
+        LOG_ERR(logger, "Gyroscope self-test failed!");
+        returnValue = false;
+    }
+
+    // Disable self test
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL5_C, 0x00);
+    // Disable sensor
+    spi.writeRegister(LSM6DSRXDefs::REG_CTRL2_G, 0x00);
+
+    return returnValue;
+}
+
+bool LSM6DSRX::checkWhoAmI()
+{
+    uint8_t regValue = 0;
+    {
+        SPITransaction transaction{spiSlave};
+        regValue = transaction.readRegister(LSM6DSRXDefs::REG_WHO_AM_I);
+    }
+
+    return regValue == LSM6DSRXDefs::WHO_AM_I_VALUE;
+}
+
+int16_t LSM6DSRX::combineHighLowBits(uint8_t low, uint8_t high)
+{
+    int16_t ret = high;
+    ret <<= 8;
+    ret |= low;
+    return ret;
+}
+
+uint16_t LSM6DSRX::combineHighLowBitsUnsigned(uint8_t low, uint8_t high)
+{
+    uint16_t sample = high;
+    sample <<= 8;
+    sample |= low;
+    return sample;
+}
+
+void LSM6DSRX::getAccelerometerData(LSM6DSRXData& data)
+{
+    data.accelerationTimestamp = TimestampTimer::getTimestamp();
+
+    data.accelerationX = getAxisData(
+        LSM6DSRXDefs::REG_OUTX_L_A, LSM6DSRXDefs::REG_OUTX_H_A, sensitivityAcc);
+    data.accelerationY = getAxisData(
+        LSM6DSRXDefs::REG_OUTY_L_A, LSM6DSRXDefs::REG_OUTY_H_A, sensitivityAcc);
+    data.accelerationZ = getAxisData(
+        LSM6DSRXDefs::REG_OUTZ_L_A, LSM6DSRXDefs::REG_OUTZ_H_A, sensitivityAcc);
+}
+
+void LSM6DSRX::getGyroscopeData(LSM6DSRXData& data)
+{
+    data.angularSpeedTimestamp = TimestampTimer::getTimestamp();
+
+    data.angularSpeedX = getAxisData(
+        LSM6DSRXDefs::REG_OUTX_L_G, LSM6DSRXDefs::REG_OUTX_H_G, sensitivityGyr);
+    data.angularSpeedY = getAxisData(
+        LSM6DSRXDefs::REG_OUTY_L_G, LSM6DSRXDefs::REG_OUTY_H_G, sensitivityGyr);
+    data.angularSpeedZ = getAxisData(
+        LSM6DSRXDefs::REG_OUTZ_L_G, LSM6DSRXDefs::REG_OUTZ_H_G, sensitivityGyr);
+}
+
+uint32_t LSM6DSRX::getSensorTimestamp()
+{
+    SPITransaction spi{spiSlave};
+
+    uint32_t value = spi.readRegister(LSM6DSRXDefs::REG_TIMESTAMP0);
+    value |= spi.readRegister(LSM6DSRXDefs::REG_TIMESTAMP1) << 8;
+    value |= spi.readRegister(LSM6DSRXDefs::REG_TIMESTAMP2) << 16;
+    value |= spi.readRegister(LSM6DSRXDefs::REG_TIMESTAMP3) << 24;
+
+    return value;
+}
+
+LSM6DSRXData LSM6DSRX::getSensorData()
+{
+    D(assert(isInit && "init() was not called"));
+
+    LSM6DSRXData data;
+
+    getAccelerometerData(data);
+    getGyroscopeData(data);
+
+    return data;
+}
+
+bool LSM6DSRX::selfTest()
+{
+    D(assert(isInit && "init() was not called"));
+
+    isInit = false;
+
+    if (!selfTestAcc() || !selfTestGyr())
+    {
+        lastError = SensorErrors::SELF_TEST_FAIL;
+        return false;
+    }
+
+    return init();
+}
+
+LSM6DSRXData LSM6DSRX::sampleImpl()
+{
+    D(assert(isInit && "init() was not called"));
+    // Reset any errors.
+    lastError = SensorErrors::NO_ERRORS;
+
+    if (config.fifoMode == LSM6DSRXConfig::FIFO_MODE::BYPASS)
+    {
+        return getSensorData();
+    }
+
+    readFromFifo();
+
+    if (lastFifoLevel > 0)
+    {
+        return lastFifo[lastFifoLevel - 1];
+    }
+    else
+    {
+        // no new data
+        lastError = SensorErrors::NO_NEW_DATA;
+        return lastValidSample;
+    }
+}
+
+float LSM6DSRX::getAxisData(LSM6DSRXDefs::Registers lowReg,
+                            LSM6DSRXDefs::Registers highReg, float sensitivity)
+{
+    int8_t low = 0, high = 0;
+    int16_t sample = 0;
+
+    SPITransaction transaction{spiSlave};
+
+    high = transaction.readRegister(highReg);
+    low  = transaction.readRegister(lowReg);
+
+    sample = combineHighLowBits(low, high);
+
+    float ret = static_cast<float>(sample) * sensitivity;
+    return ret;
+}
+
+int16_t LSM6DSRX::getAxisData(LSM6DSRXDefs::Registers lowReg,
+                              LSM6DSRXDefs::Registers highReg)
+{
+    int8_t low = 0, high = 0;
+    int16_t sample = 0;
+
+    SPITransaction transaction{spiSlave};
+
+    high = transaction.readRegister(highReg);
+    low  = transaction.readRegister(lowReg);
+
+    sample = combineHighLowBits(low, high);
+
+    return sample;
+}
+
+uint64_t LSM6DSRX::convertTimestamp(const uint64_t sensorTimestamp)
+{
+    uint64_t deltaSensor = 0;  // difference between 2 sensor timestamps.
+    if (sensorTimestamp >= sensorTimestamp0)
+    {
+        deltaSensor = sensorTimestamp - sensorTimestamp0;
+    }
+    else
+    {
+        deltaSensor =
+            static_cast<uint64_t>(-1) - sensorTimestamp0 + sensorTimestamp + 1;
+    }
+
+    // delta evaluated from TimestampTimer point of view.
+    uint64_t delta = deltaSensor * sensorTimestampResolution;
+
+    return timestamp0 + delta;
+}
+
+void LSM6DSRX::correlateTimestamps()
+{
+    timestamp0       = TimestampTimer::getTimestamp();
+    sensorTimestamp0 = getSensorTimestamp();
+}
+
+float LSM6DSRX::getSensorTimestampResolution()
+{
+    SPITransaction spi{spiSlave};
+
+    uint8_t value = spi.readRegister(LSM6DSRXDefs::REG_INTERNAL_FREQ_FINE);
+
+    // TS_Res = 1 / (40000 + (0.0015 * INTERNAL_FREQ_FINE * 40000))
+    float TS_Res = 1 / (40000 + (0.0015 * value * 40000));
+    return TS_Res * 1000;
+}
+
+void LSM6DSRX::readFromFifo()
+{
+    SPITransaction spi{spiSlave};
+
+    // get number of sample to read
+    const uint16_t numSamples = unreadDataInFifo();
+
+    /**
+     * Data has 2bits tags that determins the corresponding time slot.
+     *
+     * 00 -> first element of the array (timestamps[0])
+     * 11 -> last element of the array (timestamps[3])
+     *
+     * When a new timestamp is received from the fifo the array is updated.
+     * NOTE: those timestamps are already converted from sensor ones to
+     * TimestampTimer class.
+     */
+    LSM6DSRXDefs::FifoTimeslotData
+        timestamps[LSM6DSRXDefs::FIFO_TIMESLOT_NUMBER] = {LSM6DSRXData(), false,
+                                                          false};
+
+    // read samples from the sensors
+    spi.readRegisters(LSM6DSRXDefs::REG_FIFO_DATA_OUT_TAG,
+                      reinterpret_cast<uint8_t*>(rawFifo.data()),
+                      numSamples * sizeof(LSM6DSRXDefs::RawFifoData));
+
+    // not all data extracted from fifo is sample data, timestamps are not
+    // saved.
+    // --> `i` keeps count of the number of elements in `rawFifo`
+    //     `idxFifo` keeps track of the samples saved inside `lastFifo`
+    uint16_t idxFifo = 0;
+    for (uint16_t i = 0; i < numSamples; ++i)
+    {
+        const uint8_t sensorTag   = (rawFifo[i].sampleTag >> 3) & 31;
+        const uint8_t timeslotTag = (rawFifo[i].sampleTag & 6) >> 1;
+
+        const uint8_t xl = rawFifo[i].xl;
+        const uint8_t xh = rawFifo[i].xh;
+        const uint8_t yl = rawFifo[i].yl;
+        const uint8_t yh = rawFifo[i].yh;
+        const uint8_t zl = rawFifo[i].zl;
+        const uint8_t zh = rawFifo[i].zh;
+
+        switch (sensorTag)
+        {
+            case 0x01:
+                // Gyroscope data
+
+                // Set data
+                timestamps[timeslotTag].data.angularSpeedX =
+                    static_cast<float>(combineHighLowBits(xl, xh)) *
+                    sensitivityGyr;
+                timestamps[timeslotTag].data.angularSpeedY =
+                    static_cast<float>(combineHighLowBits(yl, yh)) *
+                    sensitivityGyr;
+                timestamps[timeslotTag].data.angularSpeedZ =
+                    static_cast<float>(combineHighLowBits(zl, zh)) *
+                    sensitivityGyr;
+
+                // Set flag
+                timestamps[timeslotTag].gyrPresent = true;
+
+                // Check if we can push into fifo (both samples are present)
+                if (timestamps[timeslotTag].accPresent)
+                {
+                    pushIntoFifo(timestamps[timeslotTag], idxFifo);
+                }
+
+                break;
+            case 0x02:
+                // Accelerometer data
+                timestamps[timeslotTag].data.accelerationX =
+                    static_cast<float>(combineHighLowBits(xl, xh)) *
+                    sensitivityAcc;
+                timestamps[timeslotTag].data.accelerationY =
+                    static_cast<float>(combineHighLowBits(yl, yh)) *
+                    sensitivityAcc;
+                timestamps[timeslotTag].data.accelerationZ =
+                    static_cast<float>(combineHighLowBits(zl, zh)) *
+                    sensitivityAcc;
+
+                timestamps[timeslotTag].accPresent = true;
+
+                // Check if we can push into fifo (both samples are present)
+                if (timestamps[timeslotTag].gyrPresent)
+                {
+                    pushIntoFifo(timestamps[timeslotTag], idxFifo);
+                }
+
+                break;
+            case 0x04:
+                // timestamp data --> update timestamps
+
+                uint32_t t =
+                    static_cast<uint32_t>(combineHighLowBitsUnsigned(xl, xh));
+                t |= static_cast<uint32_t>(combineHighLowBitsUnsigned(yl, yh))
+                     << 16;
+
+                // Set new data
+                timestamps[timeslotTag].data.accelerationTimestamp =
+                    convertTimestamp(static_cast<uint64_t>(t));
+                timestamps[timeslotTag].data.angularSpeedTimestamp =
+                    timestamps[timeslotTag].data.accelerationTimestamp;
+
+                timestamps[timeslotTag].accPresent = false;
+                timestamps[timeslotTag].gyrPresent = false;
+
+                break;
+        }
+    }
+
+    // update timestamp base values
+    correlateTimestamps();
+
+    lastFifoLevel = idxFifo;
+}
+
+void LSM6DSRX::pushIntoFifo(LSM6DSRXDefs::FifoTimeslotData& timeslot,
+                            uint16_t& fifoIdx)
+{
+    // reset flags (done even if data gets discarded)
+    timeslot.accPresent = false;
+    timeslot.gyrPresent = false;
+
+    // check if data can be pushed
+    if ((fifoIdx > 0 && timeslot.data.accelerationTimestamp ==
+                            lastFifo[fifoIdx - 1].accelerationTimestamp) ||
+        timeslot.data.accelerationTimestamp == 0)
+    {
+        // the new sample has the same timestamp of the previous one or
+        // timestamp is 0 --> discarded
+        return;
+    }
+
+    // push into fifo and update index
+    lastFifo[fifoIdx] = timeslot.data;
+    ++fifoIdx;
+
+    // update lastValidSample
+    lastValidSample = timeslot.data;
+}
+
+uint16_t LSM6DSRX::unreadDataInFifo()
+{
+    uint16_t ris = 0;
+    SPITransaction spi{spiSlave};
+
+    ris = spi.readRegister(LSM6DSRXDefs::REG_FIFO_STATUS1);
+    ris = ris | (static_cast<uint16_t>(
+                     spi.readRegister(LSM6DSRXDefs::REG_FIFO_STATUS2) & 3)
+                 << 8);
+    return ris;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/LSM6DSRX/LSM6DSRX.h b/src/shared/sensors/LSM6DSRX/LSM6DSRX.h
new file mode 100644
index 000000000..a13b6802c
--- /dev/null
+++ b/src/shared/sensors/LSM6DSRX/LSM6DSRX.h
@@ -0,0 +1,242 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <diagnostic/PrintLogger.h>
+#include <drivers/spi/SPIDriver.h>
+#include <miosix.h>
+#include <sensors/Sensor.h>
+
+#include "LSM6DSRXConfig.h"
+#include "LSM6DSRXData.h"
+#include "LSM6DSRXDefs.h"
+
+namespace Boardcore
+{
+
+/**
+ * @brief LSM6DSRX Driver.
+ */
+class LSM6DSRX : public SensorFIFO<LSM6DSRXData, LSM6DSRXDefs::FIFO_SIZE>
+{
+public:
+    /**
+     * @brief LSM6DSRX constructor.
+     *
+     * @param bus SPI bus.
+     * @param csPin SPI chip select pin.
+     * @param busConfiguration SPI bus configuration.
+     * @param config LSM6DSRX configuration.
+     */
+    LSM6DSRX(SPIBus& bus, miosix::GpioPin csPin, SPIBusConfig busConfiguration,
+             LSM6DSRXConfig& config);
+
+    /**
+     * @brief Initialize the sensor.
+     */
+    bool init() override;
+
+    /**
+     * @brief Performs self test for the sensor.
+     * @return Return true if the test was successful.
+     */
+    bool selfTest() override;
+
+    /**
+     * @brief Returns data from the sensors.
+     */
+    LSM6DSRXData getSensorData();
+
+    /**
+     * @brief Gather data from FIFO/data registers.
+     */
+    LSM6DSRXData sampleImpl() override;
+
+private:
+    bool isInit = false;
+    SPISlave spiSlave;
+    LSM6DSRXConfig config;
+
+    float sensitivityAcc = 0.0;  ///< Sensitivity value for the accelerator.
+    float sensitivityGyr = 0.0;  ///< Sensitivity value for the gyroscope.
+
+    /**
+     * @brief These two values represent the same instant from the two different
+     * clocks: the one on the sensor and the one from TimestampTimer. They are
+     * used to convert a sensor timestamp to a TimestampTimer one.
+     */
+    uint64_t timestamp0 = 0;  ///< Timestamp given by TimestampTimer class.
+    uint32_t sensorTimestamp0 = 0;  ///< Timestamp given by the sensor.
+
+    float sensorTimestampResolution =
+        0.0;  ///< Resolution of the sensor's timestamps in microseconds.
+
+    /**
+     * @brief When extracting data from fifo i get data only from one sensor,
+     * but the struct LSM6DSRXData is made to have data from both sensors. The
+     * idea is to copy the value from the last valid sample for the sensor that
+     * hasn't received data.
+     */
+    LSM6DSRXData lastValidSample;
+
+    /**
+     * @brief When extracting samples from fifo data is first read and saved
+     * inside this array, then it get processed and stored inside `lastFifo`.
+     */
+    std::array<LSM6DSRXDefs::RawFifoData, LSM6DSRXDefs::SENSOR_FIFO_SIZE>
+        rawFifo;
+
+    PrintLogger logger = Logging::getLogger("lsm6dsrx");
+
+    /**
+     * @brief Check who_am_i register for validity.
+     *
+     * @return Returns false if not valid.
+     */
+    bool checkWhoAmI();
+
+    /**
+     * @brief Performs a really simple reading from the FIFO buffer.
+     */
+    void readFromFifo();
+
+    /**
+     * @brief Utility function to handle the saving of a sample inside the fifo
+     * `lastFifo`. This operation could fail if the data to be pushed has the
+     * same timestamp of the last value pushed inside `lastFifo` or the
+     * timestamp is 0: in this case nothing happens to the fifo, data inside
+     * `timeslot` is discarded.
+     *
+     * @param timeslot The timeslot containing data to be pushed inside the
+     * fifo.
+     * @param fifoIdx The current index of the fifo, pointing to the current
+     * writable cell. Updated if operation is successful, unchanged otherwise.
+     */
+    void pushIntoFifo(LSM6DSRXDefs::FifoTimeslotData& timeslot,
+                      uint16_t& fifoIdx);
+
+    /**
+     * @brief Returns the number of unread data in the fifo.
+     */
+    uint16_t unreadDataInFifo();
+
+    /**
+     * @brief Utility for combining two 8 bits numbers in one 16 bits number.
+     * @param low Low bits of the 16 bit number.
+     * @param high High bits of the 16 bit number.
+     */
+    int16_t combineHighLowBits(uint8_t low, uint8_t high);
+
+    /**
+     * @brief Utility for combining two 8 bits numbers in one 16 bits number.
+     * @param low Low bits of the 16 bit number.
+     * @param high High bits of the 16 bit number.
+     */
+    uint16_t combineHighLowBitsUnsigned(uint8_t low, uint8_t high);
+
+    /**
+     * @brief Reads 16-bits float data from the specified registers.
+     * @param lowReg Register containing the low bits of the output.
+     * @param highReg Register containing the high bits of the output.
+     * @param sensitivity Sensitivity value for the sample.
+     */
+    float getAxisData(LSM6DSRXDefs::Registers lowReg,
+                      LSM6DSRXDefs::Registers highReg, float sensitivity);
+
+    /**
+     * @brief Reads 16-bits data from the specified registers.
+     * @param lowReg Register containing the low bits of the output.
+     * @param highReg Register containing the high bits of the output.
+     */
+    int16_t getAxisData(LSM6DSRXDefs::Registers lowReg,
+                        LSM6DSRXDefs::Registers highReg);
+
+    /**
+     * @brief Initialize the accelerometer.
+     */
+    void initAccelerometer();
+
+    /**
+     * @brief Initialize the gyroscope.
+     */
+    void initGyroscope();
+
+    /**
+     * @brief Initialize fifo.
+     */
+    void initFifo();
+
+    /**
+     * @brief Initialize interrupts.
+     */
+    void initInterrupts();
+
+    /**
+     * @brief Performs self test for the accelerometer.
+     * @return Return true if successful.
+     */
+    bool selfTestAcc();
+
+    /**
+     * @brief Performs self test for the gyroscope.
+     * @return Return true if successful.
+     */
+    bool selfTestGyr();
+
+    /**
+     * @brief Retrieves data from the accelerometer.
+     * @param data The structure where data from the sensor is to be saved.
+     */
+    void getAccelerometerData(LSM6DSRXData& data);
+
+    /**
+     * @brief Retrieves data from the gyroscope.
+     * @param data The structure where data from the sensor is to be saved.
+     */
+    void getGyroscopeData(LSM6DSRXData& data);
+
+    /**
+     * @brief Converts timestamp from the value given by the sensor to the one
+     * given by TimestampTimer class.
+     * @param sensorTimestamp The timestamp from the sensor to be converted into
+     * a TimestampTimer one.
+     */
+    uint64_t convertTimestamp(const uint64_t sensorTimestamp);
+
+    /**
+     * @brief Utility to set timestamp values for conversion.
+     */
+    void correlateTimestamps();
+
+    /**
+     * @brief Returns the timestamp from the sensor.
+     */
+    uint32_t getSensorTimestamp();
+
+    /**
+     * @brief Returns the timestamp resolution in milliseconds.
+     */
+    float getSensorTimestampResolution();
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/LSM6DSRX/LSM6DSRXConfig.h b/src/shared/sensors/LSM6DSRX/LSM6DSRXConfig.h
new file mode 100644
index 000000000..2db415c0f
--- /dev/null
+++ b/src/shared/sensors/LSM6DSRX/LSM6DSRXConfig.h
@@ -0,0 +1,210 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+namespace Boardcore
+{
+
+struct LSM6DSRXConfig
+{
+
+    /**
+     * @brief Output data rate definitions for the accelerometer.
+     *
+     * If OPERATING_MODE is set to NORMAL, the power mode depends on the choosen
+     * odr.
+     */
+    enum class ACC_ODR : uint8_t
+    {
+        POWER_DOWN = 0,   ///< Disabled
+        HZ_1_6     = 11,  ///< ODR of 1.6 Hz - AVAILABLE ONLY IN LOW POWER MODE
+        HZ_12_5    = 1,   ///< ODR of 12.5 Hz - low power
+        HZ_26      = 2,   ///< ODR of 26 Hz - low power
+        HZ_52      = 3,   ///< ODR of 52 Hz - low power
+        HZ_104     = 4,   ///< ODR of 104 Hz - normal
+        HZ_208     = 5,   ///< ODR of 208 Hz - normal
+        HZ_416     = 6,   ///< ODR of 416 Hz - high performance
+        HZ_833     = 7,   ///< ODR of 833 Hz - high performance
+        HZ_1660    = 8,   ///< ODR of 1.66 kHz - high performance
+        HZ_3330    = 9,   ///< ODR of 3.33 kHz - high performance
+        HZ_6660    = 10,  ///< ODR of 6.66 kHz - high performance
+    };
+
+    /**
+     * @brief Output data rate definitions for the gyroscope.
+     *
+     * If OPERATING_MODE is set to NORMAL, the power mode depends on the choosen
+     * odr.
+     */
+    enum class GYR_ODR : uint8_t
+    {
+        POWER_DOWN = 0,   ///< Disabled
+        HZ_12_5    = 1,   ///< ODR of 12.5 Hz - low power
+        HZ_26      = 2,   ///< ODR of 26 Hz - low power
+        HZ_52      = 3,   ///< ODR of 52 Hz - low power
+        HZ_104     = 4,   ///< ODR of 104 Hz - normal
+        HZ_208     = 5,   ///< ODR of 208 Hz - normal
+        HZ_416     = 6,   ///< ODR of 416 Hz - high performance
+        HZ_833     = 7,   ///< ODR of 833 Hz - high performance
+        HZ_1660    = 8,   ///< ODR of 1.66 kHz - high performance
+        HZ_3330    = 9,   ///< ODR of 3.33 kHz - high performance
+        HZ_6660    = 10,  ///< ODR of 6.66 kHz - high performance
+    };
+
+    /**
+     * @brief Fullscale values for the accelerometer.
+     */
+    enum class ACC_FULLSCALE : uint8_t
+    {
+        G2  = 0,
+        G4  = 2,
+        G8  = 3,
+        G16 = 1,
+    };
+
+    /**
+     * @brief Fullscale values for the gyroscope.
+     */
+    enum class GYR_FULLSCALE : uint8_t
+    {
+        DPS_125  = 0b0010,
+        DPS_250  = 0,
+        DPS_500  = 0b0100,
+        DPS_1000 = 0b1000,
+        DPS_2000 = 0b1100,
+        DPS_4000 = 0b0001,
+    };
+
+    /**
+     * @brief Data update mode for the sensor.
+     */
+    enum class BDU : uint8_t
+    {
+        CONTINUOUS_UPDATE = 0,
+        UPDATE_AFTER_READ = 1,  ///< Output registers are not updated until MSB
+                                ///< and LSB have been read
+    };
+
+    /**
+     * @brief Operating mode for the sensor.
+     *
+     * The sensor operates in 4 modes: power_down, low_power, normal,
+     * high_performance. high_performance is valid for all the odr values. If
+     * NORMAL mode is selected, the sensor behaviour depends on the selected
+     * odr.
+     */
+    enum class OPERATING_MODE : uint8_t
+    {
+        HIGH_PERFORMANCE = 0,  ///< Valid for all odrs
+        NORMAL = 1,  ///< Works in low power or normal mode depending on the odr
+    };
+
+    /**
+     * @brief Fifo operating mode.
+     */
+    enum class FIFO_MODE : uint8_t
+    {
+        BYPASS     = 0,  ///< Fifo disabled.
+        FIFO       = 1,  ///< Stops collecting data when FIFO is full.
+        CONTINUOUS = 6,  ///< If the FIFO is full, the new sample overwrites the
+                         ///< older one.
+    };
+
+    /**
+     * @brief Selects decimation for timestamp batching in FIFO. Write rate will
+     * be the maximum rate between accelerometer and gyro BDR divided by
+     * decimation decoder.
+     */
+    enum class FIFO_TIMESTAMP_DECIMATION : uint8_t
+    {
+        DEC_1 = 1,  ///< max(BDR_XL[Hz],BDR_GY[Hz]) [Hz]
+
+        // Those options below are disabled, because without DEC_1 the driver
+        // would not work properly (multiple samples would share the same
+        // timestamp or have none)
+
+        // DISABLED = 0,  ///< Timestamp not batched in FIFO.
+        // DEC_8    = 2,  ///< max(BDR_XL[Hz],BDR_GY[Hz]) / 8 [Hz]
+        // DEC_32   = 3,  ///< max(BDR_XL[Hz],BDR_GY[Hz]) / 32 [Hz]
+    };
+
+    /**
+     * @brief Selects batch data rate in FIFO for temperature data.
+     */
+    enum class FIFO_TEMPERATURE_BDR : uint8_t
+    {
+        DISABLED = 0,  ///< Temperature not batched in FIFO.
+
+        // commented so that temperature doesn't get sampled in fifo.
+        // HZ_1_6   = 1,  ///< 1.6 Hz
+        // HZ_12_5  = 2,  ///< 12.5 Hz
+        // HZ_52    = 3,  ///< 52 Hz
+    };
+
+    /**
+     * @brief Selectable interrupts. You can combine multiple interrupts with
+     * bitwise or.
+     */
+    enum class INTERRUPT : uint8_t
+    {
+        NOTHING        = 0,   ///< no interrupt selected
+        ACC_DRDY       = 1,   ///< Accelerometer data ready.
+        GYR_DRDY       = 2,   ///< Gyroscope data ready.
+        FIFO_THRESHOLD = 8,   ///< FIFO threshold interrupt.
+        FIFO_OVERRUN   = 16,  ///< FIFO overrun interrupt.
+        FIFO_FULL      = 32,  ///< FIFO full interrupt.
+    };
+
+    BDU bdu;  ///< Data update mode.
+
+    // accelerometer
+    ACC_ODR odrAcc;            ///< Accelerometer odr.
+    OPERATING_MODE opModeAcc;  ///< Operating mode for the accelerometer.
+    ACC_FULLSCALE fsAcc;       ///< Fullscale selection for the accelerometer.
+
+    // gyroscope
+    GYR_ODR odrGyr;            ///< Accelerometer odr.
+    OPERATING_MODE opModeGyr;  ///< Operating mode for the accelerometer.
+    GYR_FULLSCALE fsGyr;       ///< Fullscale selection for the accelerometer.
+
+    // fifo
+    FIFO_MODE fifoMode;  ///< Fifo operating mode.
+    FIFO_TIMESTAMP_DECIMATION
+    fifoTimestampDecimation;  ///< decimation for timestamp sampling in FIFO.
+    FIFO_TEMPERATURE_BDR
+    fifoTemperatureBdr;  ///< batch data rate for temperature data in FIFO.
+
+    // interrupt
+    INTERRUPT int1InterruptSelection;  ///< interrupts selected on INT1 pin.
+    INTERRUPT int2InterruptSelection;  ///< interrupts selected on INT2 pin.
+
+    /**
+     * @brief Fifo watermark level, expressed as number of samples.
+     * Max value is 170.
+     */
+    uint16_t fifoWatermark;
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/LSM6DSRX/LSM6DSRXData.h b/src/shared/sensors/LSM6DSRX/LSM6DSRXData.h
new file mode 100644
index 000000000..0cd5083fb
--- /dev/null
+++ b/src/shared/sensors/LSM6DSRX/LSM6DSRXData.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <sensors/SensorData.h>
+
+namespace Boardcore
+{
+
+struct LSM6DSRXData : public AccelerometerData, public GyroscopeData
+{
+    LSM6DSRXData()
+        : AccelerometerData{0, 0.0, 0.0, 0.0}, GyroscopeData{0, 0.0, 0.0, 0.0}
+    {
+    }
+
+    static std::string header()
+    {
+        return "accelerationTimestamp,accelerationX,accelerationY,"
+               "accelerationZ,angularSpeedTimestamp,angularSpeedX,"
+               "angularSpeedY,angularSpeedZ\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << accelerationTimestamp << "," << accelerationX << ","
+           << accelerationY << "," << accelerationZ << ","
+           << angularSpeedTimestamp << "," << angularSpeedX << ","
+           << angularSpeedY << "," << angularSpeedZ << "\n";
+    }
+};
+
+struct LSM6DSRXTemperature : public TemperatureData
+{
+    static std::string header() { return "temperatureTimestamp,temperature\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << temperatureTimestamp << "," << temperature << "\n";
+    }
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/LSM6DSRX/LSM6DSRXDefs.h b/src/shared/sensors/LSM6DSRX/LSM6DSRXDefs.h
new file mode 100644
index 000000000..43d0dd63a
--- /dev/null
+++ b/src/shared/sensors/LSM6DSRX/LSM6DSRXDefs.h
@@ -0,0 +1,188 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+namespace Boardcore
+{
+
+namespace LSM6DSRXDefs
+{
+
+/**
+ * @brief Driver's fifo size expressed as number of samples.
+ */
+const uint16_t FIFO_SIZE = 200;
+
+/**
+ * @brief Size of the sensor's internal fifo.
+ */
+const uint16_t SENSOR_FIFO_SIZE = 512;
+
+/**
+ * @brief Sensor who_am_i register value.
+ */
+const uint8_t WHO_AM_I_VALUE = 0x6B;
+
+/**
+ * @brief Lower bound value for accelerometer self test.
+ */
+const float ACC_SELF_TEST_MIN = 40.0;
+
+/**
+ * @brief Upper bound value for accelerometer self test.
+ */
+const float ACC_SELF_TEST_MAX = 1700.0;
+
+/**
+ * @brief Lower bound value for gyroscope self test.
+ * Valid for self test fullscale = 2000dps.
+ */
+const float GYR_SELF_TEST_MIN = 150.0;
+
+/**
+ * @brief Upper bound value for gyroscope self test.
+ * Valid for self test fullscale = 2000dps.
+ */
+const float GYR_SELF_TEST_MAX = 700.0;
+
+/**
+ * @brief Number of timeslots available when extracting
+ * data from fifo.
+ */
+const uint8_t FIFO_TIMESLOT_NUMBER = 4;
+
+/**
+ * @brief Internal registers definitions.
+ */
+enum Registers
+{
+    REG_WHO_AM_I = 0x0F,  ///< who_am_i register
+
+    REG_CTRL1_XL = 0x10,  ///< accelerometer control register
+    REG_CTRL2_G  = 0x11,  ///< gyroscope control register
+    REG_CTRL3_C  = 0x12,  ///< set bdu
+    REG_CTRL6_C  = 0x15,  ///< enable/disable high performance mode for the
+                          ///< accelerometer
+    REG_CTRL7_G = 0x16,   ///< enable/disable high performance mode for the
+                          ///< gyroscope
+
+    REG_FIFO_CTRL1 = 0x07,
+    REG_FIFO_CTRL2 = 0x08,
+
+    REG_FIFO_CTRL3 = 0x09,  ///< fifo control register 3 (select batch data
+                            ///< rate for gyro and acc)
+    REG_FIFO_CTRL4 = 0x0A,  ///< fifo control register 4 (select fifo mode,
+                            ///< batch data rate for temperature sensor and the
+                            ///< decimation factor for timestamp batching)
+
+    REG_FIFO_STATUS1 =
+        0x3A,  ///< Gives number of unread sensor data stored in FIFO.
+    REG_FIFO_STATUS2 = 0x3B,  ///< Gives number of unread sensor data and
+                              ///< the current status (watermark, overrun,
+                              ///< full, BDR counter) of the FIFO.
+
+    REG_STATUS   = 0x1E,  ///< data ready register.
+    REG_CTRL4_C  = 0x13,
+    REG_CTRL5_C  = 0x14,
+    REG_CTRL8_XL = 0x17,
+    REG_CTRL9_XL = 0x18,
+    REG_CTRL10_C = 0x19,
+
+    REG_FIFO_DATA_OUT_TAG = 0x78,
+    REG_FIFO_DATA_OUT_X_L = 0x79,
+    REG_FIFO_DATA_OUT_X_H = 0x7A,
+    REG_FIFO_DATA_OUT_Y_L = 0x7B,
+    REG_FIFO_DATA_OUT_Y_H = 0x7C,
+    REG_FIFO_DATA_OUT_Z_L = 0x7D,
+    REG_FIFO_DATA_OUT_Z_H = 0x7E,
+
+    REG_OUTX_L_A =
+        0x28,  ///< Low bits output register for the accelerometer (x axis)
+    REG_OUTX_H_A =
+        0x29,  ///< High bits output register for the accelerometer (x axis)
+    REG_OUTY_L_A =
+        0x2A,  ///< Low bits output register for the accelerometer (y axis)
+    REG_OUTY_H_A =
+        0x2B,  ///< High bits output register for the accelerometer (y axis)
+    REG_OUTZ_L_A =
+        0x2C,  ///< Low bits output register for the accelerometer (z axis)
+    REG_OUTZ_H_A =
+        0x2D,  ///< High bits output register for the accelerometer (z axis)
+
+    REG_OUTX_L_G =
+        0x22,  ///< Low bits output register for the gyroscope (x axis)
+    REG_OUTX_H_G =
+        0x23,  ///< High bits output register for the gyroscope (x axis)
+    REG_OUTY_L_G =
+        0x24,  ///< Low bits output register for the gyroscope (y axis)
+    REG_OUTY_H_G =
+        0x25,  ///< High bits output register for the gyroscope (y axis)
+    REG_OUTZ_L_G =
+        0x26,  ///< Low bits output register for the gyroscope (z axis)
+    REG_OUTZ_H_G =
+        0x27,  ///< High bits output register for the gyroscope (z axis)
+
+    REG_TIMESTAMP0 = 0x40,
+    REG_TIMESTAMP1 = 0x41,
+    REG_TIMESTAMP2 = 0x42,
+    REG_TIMESTAMP3 = 0x43,
+
+    REG_INTERNAL_FREQ_FINE = 0x63,
+
+    REG_INT1_CTRL = 0x0D,  ///< set interrupts on INT1 pin.
+    REG_INT2_CTRL = 0x0E,  ///< set interrupts on INT2 pin.
+};
+
+/**
+ * @brief Structure used to store data from fifo before being processed.
+ */
+struct RawFifoData
+{
+    uint8_t sampleTag;  ///< Tag used to identify data
+    uint8_t xl;         ///< low part of DATA_OUT_X register
+    uint8_t xh;         ///< high part of DATA_OUT_X register
+    uint8_t yl;         ///< low part of DATA_OUT_Y register
+    uint8_t yh;         ///< high part of DATA_OUT_Y register
+    uint8_t zl;         ///< low part of DATA_OUT_Z register
+    uint8_t zh;         ///< high part of DATA_OUT_Z register
+};
+
+/**
+ * @brief Temporary struct used to store data extracted from fifo, before
+ * turning it into LSM6DSRXData.
+ *
+ * accPresent and gyrPresent are flags that are set when a sample from the
+ * corresponding sensor is pushed inside the timeslot.
+ */
+struct FifoTimeslotData
+{
+    LSM6DSRXData data;
+    bool accPresent = false;
+    bool gyrPresent = false;
+};
+
+}  // namespace LSM6DSRXDefs
+
+}  // namespace Boardcore
diff --git a/src/tests/sensors/test-lsm6dsrx.cpp b/src/tests/sensors/test-lsm6dsrx.cpp
new file mode 100644
index 000000000..ebaa8d668
--- /dev/null
+++ b/src/tests/sensors/test-lsm6dsrx.cpp
@@ -0,0 +1,310 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <drivers/spi/SPIDriver.h>
+#include <drivers/timer/TimestampTimer.h>
+#include <miosix.h>
+#include <sensors/LSM6DSRX/LSM6DSRX.h>
+#include <utils/Debug.h>
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <vector>
+
+using namespace Boardcore;
+using namespace miosix;
+
+/**
+ * @brief Test how much time it takes to fill completely the fifo from empty.
+ */
+void testFifoFillingTime(SPIBus& bus, miosix::GpioPin csPin,
+                         SPIBusConfig busConfiguration, LSM6DSRXConfig& config,
+                         miosix::GpioPin intPin);
+
+/**
+ * @brief Test the execution time of sampleImpl().
+ */
+void testSampleImplTime(SPIBus& bus, miosix::GpioPin csPin,
+                        SPIBusConfig busConfiguration, LSM6DSRXConfig& config);
+
+/**
+ * @brief Test fifo read.
+ */
+void testFifoRead(SPIBus& bus, miosix::GpioPin csPin,
+                  SPIBusConfig busConfiguration, LSM6DSRXConfig& config,
+                  miosix::GpioPin intPin);
+
+int main()
+{
+
+    SPIBus bus(SPI3);
+
+    GpioPin csPin(GPIOE_BASE, 3);  // PE3 CS
+    csPin.mode(Mode::OUTPUT);
+
+    GpioPin clockPin(GPIOB_BASE, 3);  // PB3 CK (SCL)
+    clockPin.mode(Mode::ALTERNATE);
+    clockPin.alternateFunction(6);
+    GpioPin misoPin(GPIOB_BASE, 4);  // PB4 MISO (SDO)
+    misoPin.alternateFunction(6);
+    misoPin.mode(Mode::ALTERNATE);
+    GpioPin mosiPin(GPIOB_BASE, 5);  // PB5 MOSI (SDA)
+    mosiPin.alternateFunction(6);
+    mosiPin.mode(Mode::ALTERNATE);
+
+    GpioPin int1Pin(GPIOC_BASE, 15);  // PC15 interrupt pin 1
+    int1Pin.mode(Mode::INPUT);
+    GpioPin int2Pin(GPIOC_BASE, 13);  // PC13 interrupt pin 2
+    int2Pin.mode(Mode::INPUT);
+
+    SPIBusConfig busConfiguration;  // Bus configuration for the sensor
+    busConfiguration.clockDivider = SPI::ClockDivider::DIV_2;
+    busConfiguration.mode =
+        SPI::Mode::MODE_0;  // Set clock polarity to 0 and phase to 1
+
+    LSM6DSRXConfig sensConfig;
+    sensConfig.bdu = LSM6DSRXConfig::BDU::CONTINUOUS_UPDATE;
+
+    // acc
+    sensConfig.fsAcc     = LSM6DSRXConfig::ACC_FULLSCALE::G2;
+    sensConfig.odrAcc    = LSM6DSRXConfig::ACC_ODR::HZ_833;
+    sensConfig.opModeAcc = LSM6DSRXConfig::OPERATING_MODE::NORMAL;
+
+    // gyr
+    sensConfig.fsGyr     = LSM6DSRXConfig::GYR_FULLSCALE::DPS_125;
+    sensConfig.odrGyr    = LSM6DSRXConfig::GYR_ODR::HZ_833;
+    sensConfig.opModeGyr = LSM6DSRXConfig::OPERATING_MODE::NORMAL;
+
+    // fifo
+    sensConfig.fifoMode = LSM6DSRXConfig::FIFO_MODE::CONTINUOUS;
+    sensConfig.fifoTimestampDecimation =
+        LSM6DSRXConfig::FIFO_TIMESTAMP_DECIMATION::DEC_1;
+    sensConfig.fifoTemperatureBdr =
+        LSM6DSRXConfig::FIFO_TEMPERATURE_BDR::DISABLED;
+
+    // interrupt
+    sensConfig.int1InterruptSelection = LSM6DSRXConfig::INTERRUPT::NOTHING;
+    sensConfig.int2InterruptSelection =
+        LSM6DSRXConfig::INTERRUPT::FIFO_THRESHOLD;
+    sensConfig.fifoWatermark = 170;
+
+    testFifoRead(bus, csPin, busConfiguration, sensConfig, int2Pin);
+
+    // testSampleImplTime(bus, csPin, busConfiguration, sensConfig);
+
+    // testFifoFillingTime(bus, csPin, busConfiguration, sensConfig, int2Pin);
+
+    while (true)
+    {
+        Thread::sleep(5000);
+    }
+
+    return 0;
+}
+
+void testFifoFillingTime(SPIBus& bus, miosix::GpioPin csPin,
+                         SPIBusConfig busConfiguration,
+                         LSM6DSRXConfig& sensConfig, miosix::GpioPin intPin)
+{
+    struct odrs
+    {
+        LSM6DSRXConfig::ACC_ODR acc;
+        LSM6DSRXConfig::GYR_ODR gyr;
+    };
+
+    odrs arr[] = {
+        {LSM6DSRXConfig::ACC_ODR::HZ_12_5, LSM6DSRXConfig::GYR_ODR::HZ_12_5},
+        {LSM6DSRXConfig::ACC_ODR::HZ_26, LSM6DSRXConfig::GYR_ODR::HZ_26},
+        {LSM6DSRXConfig::ACC_ODR::HZ_52, LSM6DSRXConfig::GYR_ODR::HZ_52},
+        {LSM6DSRXConfig::ACC_ODR::HZ_104, LSM6DSRXConfig::GYR_ODR::HZ_104},
+        {LSM6DSRXConfig::ACC_ODR::HZ_208, LSM6DSRXConfig::GYR_ODR::HZ_208},
+        {LSM6DSRXConfig::ACC_ODR::HZ_416, LSM6DSRXConfig::GYR_ODR::HZ_416},
+        {LSM6DSRXConfig::ACC_ODR::HZ_833, LSM6DSRXConfig::GYR_ODR::HZ_833},
+        {LSM6DSRXConfig::ACC_ODR::HZ_1660, LSM6DSRXConfig::GYR_ODR::HZ_1660},
+        {LSM6DSRXConfig::ACC_ODR::HZ_3330, LSM6DSRXConfig::GYR_ODR::HZ_3330},
+        {LSM6DSRXConfig::ACC_ODR::HZ_6660, LSM6DSRXConfig::GYR_ODR::HZ_6660},
+    };
+
+    for (unsigned int odrIdx = 0; odrIdx < sizeof(arr) / sizeof(odrs); ++odrIdx)
+    {
+        sensConfig.odrAcc = arr[odrIdx].acc;
+        sensConfig.odrGyr = arr[odrIdx].gyr;
+
+        std::unique_ptr<LSM6DSRX> sens = std::make_unique<LSM6DSRX>(
+            bus, csPin, busConfiguration, sensConfig);
+        if (sens->init() == false)
+        {
+            while (true)
+            {
+                TRACE("Error, sensor not initialized at index %d\n\n", odrIdx);
+                Thread::sleep(2000);
+            }
+        }
+
+        // empty the fifo
+        sens->sampleImpl();
+
+        // test time needed to fill the fifo
+        uint64_t t0   = TimestampTimer::getTimestamp();
+        int dataReady = intPin.value();
+        while (dataReady != 1)
+        {
+            Thread::sleep(1);
+            dataReady = intPin.value();
+        }
+        uint64_t t1 = TimestampTimer::getTimestamp();
+
+        uint64_t diff = t1 - t0;
+
+        std::cout << odrIdx << ") Filling time(us): " << diff << "\n\n";
+    }
+}
+
+void testSampleImplTime(SPIBus& bus, miosix::GpioPin csPin,
+                        SPIBusConfig busConfiguration,
+                        LSM6DSRXConfig& sensConfig)
+{
+    std::unique_ptr<LSM6DSRX> sens =
+        std::make_unique<LSM6DSRX>(bus, csPin, busConfiguration, sensConfig);
+
+    if (sens->init() == false)
+    {
+        while (true)
+        {
+            TRACE("Error, sensor not initialized\n\n");
+            Thread::sleep(2000);
+        }
+    }
+
+    if (sens->selfTest())
+    {
+        TRACE("Self test successful\n\n");
+        Thread::sleep(2000);
+    }
+    else
+    {
+        TRACE("Self test failed\n\n");
+        while (true)
+        {
+            Thread::sleep(2000);
+        }
+    }
+
+    std::cout << "sensor initialized\n";
+    while (true)
+    {
+        uint64_t t0 = TimestampTimer::getTimestamp();
+
+        auto d = sens->sampleImpl();
+
+        uint64_t t1 = TimestampTimer::getTimestamp();
+
+        uint64_t diff = t1 - t0;
+
+        std::cout << "sampleImpl() execution time(us): " << diff << "\n";
+        std::cout << "last fifo sample:\n";
+        d.print(std::cout);
+        std::cout << "\n\n\n";
+
+        miosix::Thread::sleep(1000);
+    }
+}
+
+void testFifoRead(SPIBus& bus, miosix::GpioPin csPin,
+                  SPIBusConfig busConfiguration, LSM6DSRXConfig& config,
+                  miosix::GpioPin intPin)
+{
+    std::unique_ptr<LSM6DSRX> sens =
+        std::make_unique<LSM6DSRX>(bus, csPin, busConfiguration, config);
+
+    if (sens->init() == false)
+    {
+        while (true)
+        {
+            std::cout << "Error, sensor not initialized\n\n";
+            Thread::sleep(2000);
+        }
+    }
+
+    if (sens->selfTest())
+    {
+        std::cout << "Self test successful\n\n";
+        Thread::sleep(2000);
+    }
+    else
+    {
+        std::cout << "Self test failed\n\n";
+        while (true)
+        {
+            Thread::sleep(2000);
+        }
+    }
+
+    while (true)
+    {
+        // wait for fifo full interrupt
+        int dataReady = intPin.value();
+        while (dataReady != 1)
+        {
+            Thread::sleep(20);
+            dataReady = intPin.value();
+        }
+
+        sens->sample();
+
+        const std::array<LSM6DSRXData, LSM6DSRXDefs::FIFO_SIZE>& buf =
+            sens->getLastFifo();
+
+        // Print fifo
+        std::cout << "last fifo element:\n";
+        buf[sens->getLastFifoSize() - 1].print(std::cout);
+        std::cout << "last fifo size: " << sens->getLastFifoSize() << "\n";
+
+        // Check fifo data
+        for (uint16_t i = 1; i < sens->getLastFifoSize(); ++i)
+        {
+            // Check for accelerometer timestamps
+            if (buf[i].accelerationTimestamp <=
+                buf[i - 1].accelerationTimestamp)
+            {
+                std::cout << "Error, accelerometer data not ordered\n\n";
+            }
+
+            // Check for gyroscope timestamps
+            if (buf[i].angularSpeedTimestamp <=
+                buf[i - 1].angularSpeedTimestamp)
+            {
+                std::cout << "Error, gyroscope data not ordered\n\n";
+            }
+
+            // Check that gyr and acc timestamps are equal
+            if (buf[i].accelerationTimestamp != buf[i].angularSpeedTimestamp)
+            {
+                std::cout << "Error, timestamps not equal\n\n";
+            }
+        }
+
+        std::cout << "Extraction completed\n\n" << std::endl;
+        Thread::sleep(1000);
+    }
+}
-- 
GitLab