From a978b4a6c8c9ff9c6f5fecafec5a4fbbee798a1c Mon Sep 17 00:00:00 2001
From: Raul Radu <raul.radu@skywarder.eu>
Date: Tue, 6 Jun 2023 17:46:41 +0000
Subject: [PATCH] [H3LIS331DL] Driver for the H3LIS331DL High-G accelerometer

---
 CMakeLists.txt                                |   2 +
 cmake/boardcore.cmake                         |   1 +
 src/shared/sensors/H3LIS331DL/H3LIS331DL.cpp  | 178 ++++++++++++++++++
 src/shared/sensors/H3LIS331DL/H3LIS331DL.h    | 150 +++++++++++++++
 .../sensors/H3LIS331DL/H3LIS331DLData.h       |  52 +++++
 .../sensors/H3LIS331DL/H3LIS331DLDefs.h       | 128 +++++++++++++
 src/tests/sensors/test-h3lis331dl.cpp         | 102 ++++++++++
 7 files changed, 613 insertions(+)
 create mode 100644 src/shared/sensors/H3LIS331DL/H3LIS331DL.cpp
 create mode 100644 src/shared/sensors/H3LIS331DL/H3LIS331DL.h
 create mode 100644 src/shared/sensors/H3LIS331DL/H3LIS331DLData.h
 create mode 100644 src/shared/sensors/H3LIS331DL/H3LIS331DLDefs.h
 create mode 100644 src/tests/sensors/test-h3lis331dl.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 109b9ba29..197c8e5e5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -404,6 +404,8 @@ sbs_target(test-vn100 stm32f407vg_stm32f4discovery)
 add_executable(test-lis2mdl src/tests/sensors/test-lis2mdl.cpp)
 sbs_target(test-lis2mdl stm32f429zi_stm32f4discovery)
 
+add_executable(test-h3lis331dl src/tests/sensors/test-h3lis331dl.cpp)
+sbs_target(test-h3lis331dl stm32f407vg_stm32f4discovery)
 
 #-----------------------------------------------------------------------------#
 #                                Tests - Utils                                #
diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake
index 5daaab18e..d600f341f 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -87,6 +87,7 @@ foreach(OPT_BOARD ${BOARDS})
         ${SBS_BASE}/src/shared/sensors/BMP280/BMP280I2C.cpp
         ${SBS_BASE}/src/shared/sensors/BMX160/BMX160.cpp
         ${SBS_BASE}/src/shared/sensors/BMX160/BMX160WithCorrection.cpp
+        ${SBS_BASE}/src/shared/sensors/H3LIS331DL/H3LIS331DL.cpp
         ${SBS_BASE}/src/shared/sensors/HX711/HX711.cpp
         ${SBS_BASE}/src/shared/sensors/LIS3MDL/LIS3MDL.cpp
         ${SBS_BASE}/src/shared/sensors/LIS331HH/LIS331HH.cpp
diff --git a/src/shared/sensors/H3LIS331DL/H3LIS331DL.cpp b/src/shared/sensors/H3LIS331DL/H3LIS331DL.cpp
new file mode 100644
index 000000000..634704a74
--- /dev/null
+++ b/src/shared/sensors/H3LIS331DL/H3LIS331DL.cpp
@@ -0,0 +1,178 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 "H3LIS331DL.h"
+
+namespace Boardcore
+{
+
+H3LIS331DL::H3LIS331DL(SPIBusInterface& spiBus, miosix::GpioPin cs,
+                       SPIBusConfig cfg, H3LIS331DLDefs::OutputDataRate odr,
+                       H3LIS331DLDefs::BlockDataUpdate bdu,
+                       H3LIS331DLDefs::FullScaleRange fs)
+    : spi(spiBus, cs, cfg), odr(odr), bdu(bdu), fs(fs), initialized(false)
+{
+    spi.config.byteOrder = SPI::Order::LSB_FIRST;
+    spi.config.mode      = SPI::Mode::MODE_3;
+}
+
+H3LIS331DL::H3LIS331DL(SPIBusInterface& spiBus, miosix::GpioPin cs,
+                       H3LIS331DLDefs::OutputDataRate odr,
+                       H3LIS331DLDefs::BlockDataUpdate bdu,
+                       H3LIS331DLDefs::FullScaleRange fs)
+    : H3LIS331DL(spiBus, cs, {}, odr, bdu, fs)
+{
+}
+
+bool H3LIS331DL::init()
+{
+    if (initialized)
+    {
+        lastError = SensorErrors::ALREADY_INIT;
+        return false;
+    }
+
+    SPITransaction spiTr(spi);
+
+    uint8_t whoami =
+        spiTr.readRegister(H3LIS331DLDefs::Registers::REG_WHO_AM_I);
+
+    if (whoami != H3LIS331DLDefs::WHO_AM_I_ID)
+    {
+        lastError = SensorErrors::INVALID_WHOAMI;
+        LOG_ERR(logger,
+                "Failed init. Cause: INVALID_WHOAMI. Expected Value: "
+                "{:X}. Actual Value: {:X}\n",
+                H3LIS331DLDefs::WHO_AM_I_ID, whoami);
+        return false;
+    }
+
+    // We assume everything is okay, if there are issues while writing the
+    // configuration we will set this flag to false
+    initialized = true;
+
+    // CTRL_REG1 initialization
+    {
+        uint8_t ctrlReg1 = 0b0000'0000;  // Default: poweroff
+
+        ctrlReg1 = odr | H3LIS331DLDefs::CTRL_REG1_XEN |
+                   H3LIS331DLDefs::CTRL_REG1_YEN |
+                   H3LIS331DLDefs::CTRL_REG1_ZEN;
+
+        spiTr.writeRegister(H3LIS331DLDefs::Registers::REG_CTRL_REG1, ctrlReg1);
+
+        miosix::delayUs(10);
+        initialized &=
+            (ctrlReg1 ==
+             spiTr.readRegister(H3LIS331DLDefs::Registers::REG_CTRL_REG1));
+
+        LOG_DEBUG(logger,
+                  "Control Register 1 After init: {:X}, expected "
+                  "value:{:X}",
+                  spiTr.readRegister(H3LIS331DLDefs::Registers::REG_CTRL_REG1),
+                  ctrlReg1);
+    }
+
+    {
+        // CTRL_REG4 initialization
+        // CTRL_REG4 controls the BDU (@see H3LIS331DL::BlockDataUpdate) and
+        // the FSR (@see H3LIS331DL::FullScaleRange).
+        uint8_t ctrlReg4 = 0b0000'0000;
+
+        ctrlReg4 = bdu | fs;
+
+        spiTr.writeRegister(H3LIS331DLDefs::Registers::REG_CTRL_REG4, ctrlReg4);
+
+        miosix::delayUs(10);
+        initialized &=
+            (ctrlReg4 ==
+             spiTr.readRegister(H3LIS331DLDefs::Registers::REG_CTRL_REG4));
+        LOG_DEBUG(logger,
+                  "Control Register 4 After init: {:X}, expected "
+                  "value: {:X}",
+                  spiTr.readRegister(H3LIS331DLDefs::Registers::REG_CTRL_REG4),
+                  ctrlReg4);
+    }
+
+    return initialized;
+}
+
+bool H3LIS331DL::selfTest() { return true; }
+
+H3LIS331DLData H3LIS331DL::sampleImpl()
+{
+    if (!initialized)
+    {
+        lastError = SensorErrors::NOT_INIT;
+        return lastSample;
+    }
+
+    // Timestamp of the last sample
+    uint64_t lastSampleTimestamp = TimestampTimer::getTimestamp();
+
+    float x = 0;
+    float y = 0;
+    float z = 0;
+
+    // Read output data registers (X, Y, Z)
+    {
+        SPITransaction spiTr(spi);
+        uint8_t buff[7];
+
+        spiTr.readRegisters(H3LIS331DLDefs::Registers::REG_STATUS_REG |
+                                H3LIS331DLDefs::AUTOINC_ADDR,
+                            buff, 7);
+
+        // The status register that tells if new data is available is the first
+        // byte that was read from the 7 byte read.
+        uint8_t status = buff[0];
+        uint16_t regX  = buff[2] << 8 | buff[1];
+        uint16_t regY  = buff[4] << 8 | buff[3];
+        uint16_t regZ  = buff[6] << 8 | buff[5];
+
+        if (!(status & H3LIS331DLDefs::STATUS_REG_XYZDR))
+        {
+            lastError = SensorErrors::NO_NEW_DATA;
+            LOG_DEBUG(logger, "No new data available.");
+            return lastSample;
+        }
+
+        // Here we get the sensitivity based on the FullScaleRange
+        float sensitivity = H3LIS331DLDefs::SENSITIVITY_VALUES[fs >> 4];
+
+        int16_t xInt = static_cast<int16_t>(regX);
+        float xFloat = static_cast<float>(xInt >> 4);
+        x            = xFloat * sensitivity;
+
+        int16_t yInt = static_cast<int16_t>(regY);
+        float yFloat = static_cast<float>(yInt >> 4);
+        y            = yFloat * sensitivity;
+
+        int16_t zInt = static_cast<int16_t>(regZ);
+        float zFloat = static_cast<float>(zInt >> 4);
+        z            = zFloat * sensitivity;
+    }
+
+    return H3LIS331DLData(lastSampleTimestamp, x, y, z);
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/H3LIS331DL/H3LIS331DL.h b/src/shared/sensors/H3LIS331DL/H3LIS331DL.h
new file mode 100644
index 000000000..b8d0c11b8
--- /dev/null
+++ b/src/shared/sensors/H3LIS331DL/H3LIS331DL.h
@@ -0,0 +1,150 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <drivers/timer/TimestampTimer.h>
+#include <miosix.h>
+#include <sensors/Sensor.h>
+
+#include "H3LIS331DLData.h"
+#include "H3LIS331DLDefs.h"
+
+namespace Boardcore
+{
+
+/**
+ * Driver for H3LIS331DL, a 3-Axis, high g Accelerometer Sensor made by
+ * STMicroelectronics.
+ */
+class H3LIS331DL : public Sensor<H3LIS331DLData>
+{
+
+public:
+    /**
+     * @brief Creates an instance of an H3LIS331DL sensor
+     *
+     * @param spiBus The SPI bus the sensor is connected to
+     * @param cs The Chip Select GPIO
+     * @param odr Sensor's Output Data Rate (See datasheet)
+     * @param bdu Sensor's Block Data Update (See datasheet)
+     * @param fs Sensor's Full Scale Range (See datasheet)
+     */
+    H3LIS331DL(SPIBusInterface& spiBus, miosix::GpioPin cs,
+               H3LIS331DLDefs::OutputDataRate odr,
+               H3LIS331DLDefs::BlockDataUpdate bdu,
+               H3LIS331DLDefs::FullScaleRange fs);
+
+    /**
+     * @brief Creates an instance of an H3LIS331DL sensor
+     *
+     * @param spiBus The SPI bus the sensor is connected to
+     * @param cs  The Chip Select GPIO
+     * @param cfg SPI Bus Configuration
+     * @param odr Sensor's Output Data Rate (See datasheet)
+     * @param bdu Sensor's Block Data Update (See datasheet)
+     * @param fs Sensor's Full Scale Range (See datasheet)
+     */
+    H3LIS331DL(SPIBusInterface& spiBus, miosix::GpioPin cs, SPIBusConfig cfg,
+               H3LIS331DLDefs::OutputDataRate odr,
+               H3LIS331DLDefs::BlockDataUpdate bdu,
+               H3LIS331DLDefs::FullScaleRange fs);
+
+    /**
+     * @brief Initializes the H3LIS331DL
+     *
+     * The init function writes the configuration to the configuration
+     * registers so no further writes are needed.
+     *
+     * @returns True if the initialization was OK. Returns False otherwise (use
+     * getLastError()) method to get information about the last error.
+     */
+    bool init();
+
+    /**
+     * @brief Samples data from the register.
+     *
+     * This method reads the data from the 3 pairs (one pair for each axis) of
+     * 8bit registers and convert it to floating point value based on the Full
+     * Scale Range specified at construction time.
+     * The data is returned in the H3LIS331DLData struct correlated to a
+     * timestamp of when the data was sampled.
+     *
+     * @returns A copy of an instance of H3LIS331DLData.
+     */
+    H3LIS331DLData sampleImpl() override;
+
+    /**
+     * @brief This method does nothing as no self test is implemented in the
+     * sensor
+     *
+     * @returns True always.
+     */
+    bool selfTest();
+
+private:
+    /**
+     * @brief The SPI driver used to create SPI Transactions
+     */
+    SPISlave spi;
+
+    /**
+     * @brief The OutputDataRate that is set to the sensor.
+     *
+     * Default: 50 HZ (@see OutputDataRate::ODR_50)
+     *
+     * Note: setting the ODR also sets the PowerMode.
+     * If the ODR is less than ODR_50 low PowerMode is set.
+     * Otherwise normal PowerMode is set.
+     */
+    H3LIS331DLDefs::OutputDataRate odr;
+
+    /**
+     * @brief The BlockDataUpdate that is set to the sensor.
+     *
+     * Default: Continuos Update (@see
+     * H3LIS331DL::BlockDataUpdate::BDU_CONTINUOS_UPDATE)
+     */
+    H3LIS331DLDefs::BlockDataUpdate bdu;
+
+    /**
+     * @brief The Full Scale Range set to the sensor.
+     *
+     * Default: +-100 (@see H3LIS331DL::FullScaleRange::FS_100)
+     *
+     * Note: setting the FSR also changes the sensitivity of the sensor.
+     */
+    H3LIS331DLDefs::FullScaleRange fs;
+
+    /**
+     * @brief True if the sensor is already initialized, False otherwise.
+     *
+     * Note: This is only changed by init, read by init and sampleImpl.
+     */
+    bool initialized;
+
+    PrintLogger logger = Logging::getLogger("h3lis331dl");
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/H3LIS331DL/H3LIS331DLData.h b/src/shared/sensors/H3LIS331DL/H3LIS331DLData.h
new file mode 100644
index 000000000..054ef30c4
--- /dev/null
+++ b/src/shared/sensors/H3LIS331DL/H3LIS331DLData.h
@@ -0,0 +1,52 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 H3LIS331DLData : public AccelerometerData
+{
+
+    H3LIS331DLData() : AccelerometerData(0, 0, 0, 0){};
+
+    H3LIS331DLData(uint64_t ts, float aX, float aY, float aZ)
+        : AccelerometerData(ts, aX, aY, aZ){};
+
+    explicit H3LIS331DLData(AccelerometerData acc) : AccelerometerData(acc){};
+
+    static std::string header()
+    {
+        return "timestamp,accelerationX,accelerationY,accelerationZ\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << accelerationTimestamp << "," << accelerationX << ","
+           << accelerationY << "," << accelerationZ << "\n";
+    }
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/H3LIS331DL/H3LIS331DLDefs.h b/src/shared/sensors/H3LIS331DL/H3LIS331DLDefs.h
new file mode 100644
index 000000000..57394833e
--- /dev/null
+++ b/src/shared/sensors/H3LIS331DL/H3LIS331DLDefs.h
@@ -0,0 +1,128 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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.
+ */
+
+namespace Boardcore
+{
+
+namespace H3LIS331DLDefs
+{
+
+/**
+ * @brief Constants for the FullScale Range.
+ *
+ * Note: it also changes the sensitivity [mg/digit] (see datasheet).
+ */
+enum FullScaleRange
+{
+    FS_100 = 0 << 4,  ///< +-100 g, sensitivity: 0.049 mg/digit
+    FS_200 = 1 << 4,  ///< +-200 g, sensitivity: 0.098 mg/digit
+    FS_400 = 3 << 4   ///< +-400 g, sensitivity: 0.195 mg/digit
+};
+
+/**
+ * @brief Constants for Output Data Rate configuration.
+ *
+ * Note: The ODR also sets the Sensor's Power Mode as it is strictly
+ * dependant on the ODR (See datasheet).
+ * As the ODR is set differently based on the Power Mode the ODRs including
+ * and after ODR_50 will be offsetted to 0 by subtracting ODR_50.
+ */
+enum OutputDataRate
+{
+    ODR_LP_0_5 = 0b010 << 5,              ///<  0.5 Hz low power mode
+    ODR_LP_1   = 0b011 << 5,              ///<    1 Hz low power mode
+    ODR_LP_2   = 0b100 << 5,              ///<    2 Hz low power mode
+    ODR_LP_5   = 0b101 << 5,              ///<    5 Hz low power mode
+    ODR_LP_10  = 0b110 << 5,              ///<   10 Hz low power mode
+    ODR_50     = 0b001 << 5 | 0b00 << 3,  ///<   50 Hz normal power mode
+    ODR_100    = 0b001 << 5 | 0b01 << 3,  ///<  100 Hz normal power mode
+    ODR_400    = 0b001 << 5 | 0b10 << 3,  ///<  400 Hz normal power mode
+    ODR_1000   = 0b001 << 5 | 0b11 << 3   ///< 1000 Hz normal power mode
+};
+
+/**
+ * @brief Constants for Block Data Update
+ */
+enum BlockDataUpdate
+{
+    BDU_CONTINUOS_UPDATE = 0 << 7,
+    BDU_WAIT_UNTIL_READ  = 1 << 7
+};
+
+/**
+ * @brief Constants for the Registers
+ */
+enum Registers
+{
+    REG_WHO_AM_I   = 0x0F,
+    REG_CTRL_REG1  = 0x20,
+    REG_CTRL_REG2  = 0x21,
+    REG_CTRL_REG3  = 0x22,
+    REG_CTRL_REG4  = 0x23,
+    REG_CTRL_REG5  = 0x24,
+    REG_STATUS_REG = 0x27,
+    REG_OUT_X_L    = 0x28,
+    REG_OUT_X_H    = 0x29,
+    REG_OUT_Y_L    = 0x2a,
+    REG_OUT_Y_H    = 0x2b,
+    REG_OUT_Z_L    = 0x2c,
+    REG_OUT_Z_H    = 0x2d
+};
+
+/**
+ * @brief magic numbers for the Status Register
+ */
+enum Statuses : uint8_t
+{
+    STATUS_REG_XDR   = 0b0000'0001,  ///< Data Ready on X-Axis
+    STATUS_REG_YDR   = 0b0000'0010,  ///< Data Ready on Y-Axis
+    STATUS_REG_ZDR   = 0b0000'0100,  ///< Data Ready on Z-Axis
+    STATUS_REG_XYZDR = 0b0000'1000,  ///< Data Ready on All Axis
+    STATUS_REG_XOR   = 0b0001'0000,  ///< Data Overrun on X-Axis
+    STATUS_REG_YOR   = 0b0010'0000,  ///< Data Overrun on Y-Axis
+    STATUS_REG_ZOR   = 0b0100'0000,  ///< Data Overrun on Z-Axis
+    STATUS_REG_XYZOR = 0b1000'0000   ///< Data Overrun on All Axis
+};
+
+constexpr uint8_t CTRL_REG1_XEN = 0b001;
+constexpr uint8_t CTRL_REG1_YEN = 0b010;
+constexpr uint8_t CTRL_REG1_ZEN = 0b100;
+
+/**
+ * @brief make the driver automatically increase the register address when
+ * reading multiple bytes with SPI.
+ */
+constexpr uint8_t AUTOINC_ADDR = 0b0100'0000;
+
+const uint8_t WHO_AM_I_ID = 0x32;
+
+/**
+ * @brief Constants for the sensitivity values based on the Full Scale Range.
+ *
+ * Note: as there is no 0b10 configuration for the FSR the third value is
+ * set to 0.
+ */
+constexpr float SENSITIVITY_VALUES[] = {0.049, 0.098, 0.0, 0.195};
+
+}  // namespace H3LIS331DLDefs
+
+}  // namespace Boardcore
diff --git a/src/tests/sensors/test-h3lis331dl.cpp b/src/tests/sensors/test-h3lis331dl.cpp
new file mode 100644
index 000000000..042f56645
--- /dev/null
+++ b/src/tests/sensors/test-h3lis331dl.cpp
@@ -0,0 +1,102 @@
+/* Copyright (c) 2023 Skyward Experimental Rocketry
+ * Author: Radu Raul
+ *
+ * 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 <sensors/H3LIS331DL/H3LIS331DL.h>
+#include <utils/Debug.h>
+
+#include "miosix.h"
+
+using namespace Boardcore;
+using namespace miosix;
+
+SPIBus bus(SPI1);
+
+GpioPin spiSck(GPIOA_BASE, 5);
+GpioPin spiMiso(GPIOA_BASE, 6);
+GpioPin spiMosi(GPIOA_BASE, 7);
+GpioPin cs(GPIOE_BASE, 4);
+
+int main()
+{
+    Thread::sleep(1000);
+
+    /**
+     * I need to set the pin speed to Speed::_100MHz to solve a bug with
+     * stm32f407vg boards that prevented a correct reading from SPI (in this
+     * scenario)
+     */
+    spiSck.mode(miosix::Mode::ALTERNATE);
+    spiSck.alternateFunction(5);
+    spiSck.speed(Speed::_100MHz);
+
+    spiMiso.mode(miosix::Mode::ALTERNATE);
+    spiMiso.alternateFunction(5);
+    spiMiso.speed(Speed::_100MHz);
+
+    spiMosi.mode(miosix::Mode::ALTERNATE);
+    spiMosi.alternateFunction(5);
+    spiMosi.speed(Speed::_100MHz);
+
+    cs.mode(miosix::Mode::OUTPUT);
+    cs.high();
+
+    H3LIS331DL sensor(bus, cs, H3LIS331DLDefs::OutputDataRate::ODR_50,
+                      H3LIS331DLDefs::BlockDataUpdate::BDU_CONTINUOS_UPDATE,
+                      H3LIS331DLDefs::FullScaleRange::FS_100);
+
+    if (!sensor.init())
+    {
+        printf("Failed init!\n");
+        if (sensor.getLastError() == SensorErrors::INVALID_WHOAMI)
+        {
+            printf("Invalid WHOAMI\n");
+        }
+        return -1;
+    }
+
+    H3LIS331DLData data;
+
+    // Print out the CSV header
+    printf(H3LIS331DLData::header().c_str());
+    // sample some data from the sensor
+    for (int i = 0; i < 255; i++)
+    {
+        // sensor intitialized, should return error if no new data exist
+        sensor.sample();
+
+        // if (sensor.getLastError() == SensorErrors::NO_NEW_DATA)
+        // {
+        //     printf("\nWarning: no new data to be read \n");
+        // }
+
+        data = sensor.getLastSample();
+
+        printf("%llu,%f,%f,%f\n", data.accelerationTimestamp,
+               data.accelerationX, data.accelerationY, data.accelerationZ);
+
+        Thread::sleep(100);
+    }
+
+    return 0;
+}
-- 
GitLab