diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4fe5fc432f1f6519dd90a20b41a4aecb3df307f1..f1ed2c694c70e53b8453119b438d14b6715bef71 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -487,6 +487,9 @@ sbs_target(test-lps22df stm32f767zi_nucleo)
 add_executable(test-lsm6dsrx src/tests/sensors/test-lsm6dsrx.cpp)
 sbs_target(test-lsm6dsrx stm32f407vg_stm32f4discovery)
 
+add_executable(test-nd015x src/tests/sensors/test-nd015x.cpp)
+sbs_target(test-nd015x stm32f767zi_lyra_biscotto)
+
 #-----------------------------------------------------------------------------#
 #                                Tests - Utils                                #
 #-----------------------------------------------------------------------------#
diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake
index 2d773978aa5dbfcdb7770a23a6ab0360cc89e521..12c0eff4291a2b0d1f656dee99b25d96a76c083e 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -120,6 +120,8 @@ set(BOARDCORE_SRC
     ${BOARDCORE_PATH}/src/shared/sensors/LPS28DFW/LPS28DFW.cpp
     ${BOARDCORE_PATH}/src/shared/sensors/LPS22DF/LPS22DF.cpp
     ${BOARDCORE_PATH}/src/shared/sensors/LSM6DSRX/LSM6DSRX.cpp
+    ${BOARDCORE_PATH}/src/shared/sensors/ND015X/ND015D.cpp
+    ${BOARDCORE_PATH}/src/shared/sensors/ND015X/ND015A.cpp
 
     # Calibration
     ${BOARDCORE_PATH}/src/shared/sensors/calibration/BiasCalibration/BiasCalibration.cpp
diff --git a/src/shared/sensors/ND015X/ND015A.cpp b/src/shared/sensors/ND015X/ND015A.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..3b1a610cf303e419e016ed1f213d3b5965496cfb
--- /dev/null
+++ b/src/shared/sensors/ND015X/ND015A.cpp
@@ -0,0 +1,164 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Pietro Bortolus
+ *
+ * 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 "ND015A.h"
+
+#include <drivers/timer/TimestampTimer.h>
+#include <utils/Constants.h>
+
+#include <cmath>
+#include <string>
+
+namespace Boardcore
+{
+const char ND015A::MODEL_NAME[] = "ND015A";
+
+SPIBusConfig ND015A::getDefaultSPIConfig()
+{
+    SPIBusConfig spiConfig{};
+    spiConfig.mode         = SPI::Mode::MODE_1;
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_256;
+
+    // Datasheet specifies 100us, but 50us is enough from testing
+    spiConfig.csSetupTimeUs = 50;
+
+    // The datasheet specifies a minimum CS hold time of 100us
+    // If this is ever an issue, implement it by waiting at the next sample
+    // instead of setting it in SPIBusConfig
+
+    return spiConfig;
+}
+
+ND015A::ND015A(SPIBusInterface& bus, miosix::GpioPin cs, SPIBusConfig spiConfig,
+               IOWatchdogEnable iow, BWLimitFilter bwl, NotchEnable ntc,
+               uint8_t odr)
+    : slave(bus, cs, spiConfig), sensorSettings{0x7, iow, bwl, ntc, odr}
+{
+}
+
+bool ND015A::init()
+{
+    // setting the sensor settings to the correct values
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+
+    return true;
+}
+
+bool ND015A::selfTest() { return true; }
+
+bool ND015A::checkModelMatch()
+{
+    ND015ADataExtended extendedData{};
+    uint8_t* data = reinterpret_cast<uint8_t*>(&extendedData);
+
+    // setting the first 2 bytes of the data to the correct sensor settings
+    memcpy(&extendedData, &sensorSettings, sizeof(sensorSettings));
+
+    SPITransaction spi(slave);
+    spi.transfer(data, sizeof(extendedData));
+
+    // this part checks if the model number returned by the sensor matches the
+    // correct model number
+    bool compareResult =
+        (memcmp(&extendedData.model, &MODEL_NAME, sizeof(MODEL_NAME) - 1) == 0);
+
+    if (!compareResult)
+    {
+        auto model =
+            std::string(extendedData.model, sizeof(extendedData.model));
+        // Replace all \0 with '.' for printing
+        std::replace(model.begin(), model.end(), '\0', '.');
+
+        LOG_ERR(logger,
+                "Sensor model mismatch: received {}, expected {} (. = \\0)",
+                model, MODEL_NAME);
+        return false;
+    }
+    return true;
+}
+
+void ND015A::setOutputDataRate(uint8_t odr)
+{
+    sensorSettings.odr = odr;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+void ND015A::setIOWatchdog(IOWatchdogEnable iow)
+{
+    sensorSettings.iow = iow;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+void ND015A::setBWLimitFilter(BWLimitFilter bwl)
+{
+    sensorSettings.bwl = bwl;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+void ND015A::setNotch(NotchEnable ntc)
+{
+    sensorSettings.ntc = ntc;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+ND015XData ND015A::sampleImpl()
+{
+    ND015XData data;
+    uint16_t spiDataOut;
+    SPITransaction spi(slave);
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    uint16_t spiDataIn = spi.transfer16(spiDataOut);
+
+    data.pressure =
+        ((spiDataIn - 0.05 * pow(2, 16)) / (0.9 * pow(2, 16)) * 15) *
+        Constants::PSI_TO_PASCAL;
+    data.pressureTimestamp = TimestampTimer::getTimestamp();
+
+    return data;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/ND015X/ND015A.h b/src/shared/sensors/ND015X/ND015A.h
new file mode 100644
index 0000000000000000000000000000000000000000..77d19e4992fb99b984b5f156f204832a46c276a3
--- /dev/null
+++ b/src/shared/sensors/ND015X/ND015A.h
@@ -0,0 +1,176 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Pietro Bortolus
+ *
+ * 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 <sensors/Sensor.h>
+
+#include "ND015XData.h"
+
+namespace Boardcore
+{
+
+class ND015A : public Sensor<ND015XData>
+{
+public:
+    static const char MODEL_NAME[];
+
+    enum class IOWatchdogEnable : uint8_t
+    {
+        DISABLED = 0x00,
+        ENABLED  = 0x01,
+    };
+
+    enum class BWLimitFilter : uint8_t
+    {
+        BWL_1   = 0x00,  // 1.0 Hz
+        BWL_2   = 0x01,  // 2.0 Hz
+        BWL_5   = 0x02,  // 5.0 Hz
+        BWL_10  = 0x03,  // 10  Hz
+        BWL_20  = 0x04,  // 20  Hz
+        BWL_50  = 0x05,  // 50  Hz
+        BWL_100 = 0x06,  // 100 Hz
+        BWL_200 = 0x07,  // 200 Hz
+    };
+
+    enum class NotchEnable : uint8_t
+    {
+        DISABLED = 0x00,
+        ENABLED  = 0x01,
+    };
+
+    /**
+     * @brief Constructs the default config for the SPI bus.
+     *
+     * @return The default SPIBusConfig object.
+     */
+    static SPIBusConfig getDefaultSPIConfig();
+
+    /**
+     * @brief Constructor for the ND015A sensor.
+     *
+     * @param bus SPI bus interface.
+     * @param cs Chip select GPIO pin.
+     * @param spiConfig SPI bus configuration.
+     */
+    ND015A(SPIBusInterface& bus, miosix::GpioPin cs, SPIBusConfig spiConfig,
+           IOWatchdogEnable iow = IOWatchdogEnable::DISABLED,
+           BWLimitFilter bwl    = BWLimitFilter::BWL_200,
+           NotchEnable ntc = NotchEnable::ENABLED, uint8_t odr = 0x1C);
+
+    /**
+     * @brief Initializes the sensor.
+     *
+     * @return Always returns true.
+     */
+    bool init() override;
+
+    /**
+     * @brief Not implemented.
+     *
+     * @return Always returns true.
+     */
+    bool selfTest() override;
+
+    /**
+     * @brief function to set the output data rate
+     *
+     * @param odr   output data rate for the sensor,
+     *              the actual odr is calculated as
+     *              444Hz / odr.
+     *              Allowed values are 0x00 to 0xFF,
+     *              0x00 will select the auto-select rate mode
+     */
+    void setOutputDataRate(uint8_t odr);
+
+    /**
+     * @brief function to enable the IO watchdog
+     *
+     * @param iow  setting
+     */
+    void setIOWatchdog(IOWatchdogEnable iow);
+
+    /**
+     * @brief Sets the bandwidth limit filter for the sensor.
+     *
+     * @param bwl Bandwidth limit filter setting.
+     */
+    void setBWLimitFilter(BWLimitFilter bwl);
+
+    /**
+     * @brief Enables or disables the notch filter.
+     *
+     * @param ntc Notch filter setting.
+     */
+    void setNotch(NotchEnable ntc);
+
+    /**
+     * @brief   Checks if the sensor model matches the expected model.
+     *
+     * @return  True if the model matches, false otherwise.
+     *
+     * @warning The function might return false even when it should not as the
+     *          SPI transaction sometimes ads some zeroes when it should not.
+     *          This is because the sensore requires a clock cycle greater than
+     *          the one we can provide
+     */
+    bool checkModelMatch();
+
+protected:
+    ND015XData sampleImpl() override;
+
+private:
+    SPISlave slave;
+
+    /**
+     * @brief settings for the mode control register,
+     *        the initial values are the ones set by default
+     *        in the sensor
+     */
+    struct
+    {
+        uint8_t fsr : 3;           // full scale range, value cannot be changed
+        IOWatchdogEnable iow : 1;  // IO watchdog enable
+        BWLimitFilter bwl : 3;     // bandwidth limit filter
+        NotchEnable ntc : 1;       // notch filter enable
+        uint8_t odr : 8;           // output data rate
+    } sensorSettings;
+
+    static_assert(sizeof(sensorSettings) == 2,
+                  "sensorSettings size is not 2 bytes");
+
+    struct ND015ADataExtended
+    {
+        uint16_t pressure;
+        uint16_t temperature;
+        char model[8];
+        uint8_t serial[4];
+        uint8_t build[6];
+    };
+
+    PrintLogger logger = Logging::getLogger("nd015a");
+};
+
+}  // namespace Boardcore
+
diff --git a/src/shared/sensors/ND015X/ND015D.cpp b/src/shared/sensors/ND015X/ND015D.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..53a3ef247ac4b6055e8a6c9b79a78464d1c0ae22
--- /dev/null
+++ b/src/shared/sensors/ND015X/ND015D.cpp
@@ -0,0 +1,199 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Pietro Bortolus
+ *
+ * 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 "ND015D.h"
+
+#include <drivers/timer/TimestampTimer.h>
+#include <utils/Constants.h>
+
+#include <cmath>
+#include <string>
+
+namespace Boardcore
+{
+const char ND015D::MODEL_NAME[] = "ND015D";
+
+SPIBusConfig ND015D::getDefaultSPIConfig()
+{
+    SPIBusConfig spiConfig{};
+    spiConfig.mode         = SPI::Mode::MODE_1;
+    spiConfig.clockDivider = SPI::ClockDivider::DIV_256;
+
+    // Datasheet specifies 100us, but 50us is enough from testing
+    spiConfig.csSetupTimeUs = 50;
+
+    // The datasheet specifies a minimum CS hold time of 100us
+    // If this is ever an issue, implement it by waiting at the next sample
+    // instead of setting it in SPIBusConfig
+
+    return spiConfig;
+}
+
+ND015D::ND015D(SPIBusInterface& bus, miosix::GpioPin cs, SPIBusConfig spiConfig,
+               FullScaleRange fsr, IOWatchdogEnable iow, BWLimitFilter bwl,
+               NotchEnable ntc, uint8_t odr)
+    : slave(bus, cs, spiConfig), range(rangeToPressure(fsr)),
+      sensorSettings{fsr, iow, bwl, ntc, odr}
+{
+}
+
+bool ND015D::init()
+{
+    // setting the sensor settings to the correct values
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+
+    return true;
+}
+
+bool ND015D::selfTest() { return true; }
+
+bool ND015D::checkModelMatch()
+{
+    ND015DDataExtended extendedData{};
+    uint8_t* data = reinterpret_cast<uint8_t*>(&extendedData);
+
+    // setting the first 2 bytes of the data to the correct sensor settings
+    memcpy(&extendedData, &sensorSettings, sizeof(sensorSettings));
+
+    SPITransaction spi(slave);
+    spi.transfer(data, sizeof(extendedData));
+
+    // this part checks if the model number returned by the sensor matches the
+    // correct model number
+    bool compareResult =
+        (memcmp(&extendedData.model, &MODEL_NAME, sizeof(MODEL_NAME) - 1) == 0);
+
+    if (!compareResult)
+    {
+        auto model =
+            std::string(extendedData.model, sizeof(extendedData.model));
+        // Replace all \0 with '.' for printing
+        std::replace(model.begin(), model.end(), '\0', '.');
+
+        LOG_ERR(logger,
+                "Sensor model mismatch: received {}, expected {} (. = \\0)",
+                model, MODEL_NAME);
+        return false;
+    }
+    return true;
+}
+
+void ND015D::setOutputDataRate(uint8_t odr)
+{
+    sensorSettings.odr = odr;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+void ND015D::setFullScaleRange(FullScaleRange fsr)
+{
+    sensorSettings.fsr = fsr;
+
+    range = rangeToPressure(fsr);
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+float ND015D::rangeToPressure(FullScaleRange fsr)
+{
+    switch (fsr)
+    {
+        case ND015D::FullScaleRange::FS_1:
+            return 1;
+        case ND015D::FullScaleRange::FS_2:
+            return 2;
+        case ND015D::FullScaleRange::FS_4:
+            return 4;
+        case ND015D::FullScaleRange::FS_5:
+            return 5;
+        case ND015D::FullScaleRange::FS_10:
+            return 10;
+        case ND015D::FullScaleRange::FS_15:
+            return 15;
+        default:
+            return 0;
+    }
+}
+
+void ND015D::setIOWatchdog(IOWatchdogEnable iow)
+{
+    sensorSettings.iow = iow;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+void ND015D::setBWLimitFilter(BWLimitFilter bwl)
+{
+    sensorSettings.bwl = bwl;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+void ND015D::setNotch(NotchEnable ntc)
+{
+    sensorSettings.ntc = ntc;
+
+    SPITransaction spi(slave);
+    uint16_t spiDataOut;
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    spi.transfer16(spiDataOut);
+}
+
+ND015XData ND015D::sampleImpl()
+{
+    ND015XData data;
+    uint16_t spiDataOut;
+    SPITransaction spi(slave);
+
+    memcpy(&spiDataOut, &sensorSettings, sizeof(spiDataOut));
+    uint16_t spiDataIn = spi.transfer16(spiDataOut);
+
+    data.pressure =
+        (static_cast<int16_t>(spiDataIn) / (0.9 * pow(2, 15)) * range) *
+        Constants::PSI_TO_PASCAL;
+    data.pressureTimestamp = TimestampTimer::getTimestamp();
+
+    return data;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/ND015X/ND015D.h b/src/shared/sensors/ND015X/ND015D.h
new file mode 100644
index 0000000000000000000000000000000000000000..8a085a221516282156fbde9247be3ba927f4673d
--- /dev/null
+++ b/src/shared/sensors/ND015X/ND015D.h
@@ -0,0 +1,208 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Pietro Bortolus
+ *
+ * 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 <sensors/Sensor.h>
+
+#include "ND015XData.h"
+
+namespace Boardcore
+{
+
+class ND015D : public Sensor<ND015XData>
+{
+public:
+    static const char MODEL_NAME[];
+
+    /**
+     * The datasheet is unclear about the unit of measure,
+     * it could be either psi or inH2O but I believe it's the latter
+     */
+    enum class FullScaleRange : uint8_t
+    {
+        FS_1  = 0x02,  // 1.0  psi
+        FS_2  = 0x03,  // 2.0  psi
+        FS_4  = 0x04,  // 4.0  psi
+        FS_5  = 0x05,  // 5.0  psi
+        FS_10 = 0x06,  // 10.0 psi
+        FS_15 = 0x07,  // 15.0 psi
+    };
+
+    /**
+     * @brief Converts the FullScale value to its corresponding range.
+     *
+     * @param fsr FullScale value.
+     * @return Pressure range.
+     */
+    static float rangeToPressure(FullScaleRange fsr);
+
+    enum class IOWatchdogEnable : uint8_t
+    {
+        DISABLED = 0x00,
+        ENABLED  = 0x01,
+    };
+
+    enum class BWLimitFilter : uint8_t
+    {
+        BWL_1   = 0x00,  // 1.0 Hz
+        BWL_2   = 0x01,  // 2.0 Hz
+        BWL_5   = 0x02,  // 5.0 Hz
+        BWL_10  = 0x03,  // 10  Hz
+        BWL_20  = 0x04,  // 20  Hz
+        BWL_50  = 0x05,  // 50  Hz
+        BWL_100 = 0x06,  // 100 Hz
+        BWL_200 = 0x07,  // 200 Hz
+    };
+
+    enum class NotchEnable : uint8_t
+    {
+        DISABLED = 0x00,
+        ENABLED  = 0x01,
+    };
+
+    /**
+     * @brief Constructs the default config for the SPI bus.
+     *
+     * @return The default SPIBusConfig object.
+     */
+    static SPIBusConfig getDefaultSPIConfig();
+
+    /**
+     * @brief Constructor for the ND015D sensor.
+     *
+     * @param bus SPI bus interface.
+     * @param cs Chip select GPIO pin.
+     * @param spiConfig SPI bus configuration.
+     */
+
+    ND015D(SPIBusInterface& bus, miosix::GpioPin cs, SPIBusConfig spiConfig,
+           FullScaleRange fsr   = FullScaleRange::FS_2,
+           IOWatchdogEnable iow = IOWatchdogEnable::DISABLED,
+           BWLimitFilter bwl    = BWLimitFilter::BWL_200,
+           NotchEnable ntc = NotchEnable::ENABLED, uint8_t odr = 0x1C);
+
+    /**
+     * @brief Initializes the sensor.
+     *
+     * @return Always returns true.
+     */
+    bool init() override;
+
+    /**
+     * @brief Not implemented.
+     *
+     * @return Always returns true.
+     */
+    bool selfTest() override;
+
+    /**
+     * @brief function to set the output data rate
+     *
+     * @param odr   output data rate for the sensor,
+     *              the actual odr is calculated as
+     *              444Hz / odr.
+     *              Allowed values are 0x00 to 0xFF,
+     *              0x00 will select the auto-select rate mode
+     */
+    void setOutputDataRate(uint8_t odr);
+
+    /**
+     * @brief Sets the full-scale range for the sensor.
+     *
+     * @param fs Full-scale range.
+     */
+    void setFullScaleRange(FullScaleRange fsr);
+
+    /**
+     * @brief Enables or disables the IO watchdog.
+     *
+     * @param iow IO watchdog setting.
+     */
+    void setIOWatchdog(IOWatchdogEnable iow);
+
+    /**
+     * @brief Sets the bandwidth limit filter for the sensor.
+     *
+     * @param bwl Bandwidth limit filter setting.
+     */
+    void setBWLimitFilter(BWLimitFilter bwl);
+
+    /**
+     * @brief Enables or disables the notch filter.
+     *
+     * @param ntc Notch filter setting.
+     */
+    void setNotch(NotchEnable ntc);
+
+    /**
+     * @brief   Checks if the sensor model matches the expected model.
+     *
+     * @return  True if the model matches, false otherwise.
+     *
+     * @warning The function might return false even when it should not as the
+     *          SPI transaction sometimes ads some zeroes when it should not.
+     *          This is because the sensore requires a clock cycle greater than
+     *          the one we can provide
+     */
+    bool checkModelMatch();
+
+protected:
+    ND015XData sampleImpl() override;
+
+private:
+    SPISlave slave;
+    float range;
+
+    /**
+     * @brief settings for the mode control register,
+     *        the initial values are the ones set by default
+     *        in the sensor
+     */
+    struct
+    {
+        FullScaleRange fsr : 3;    // full scale range
+        IOWatchdogEnable iow : 1;  // IO watchdog enable
+        BWLimitFilter bwl : 3;     // bandwidth limit filter
+        NotchEnable ntc : 1;       // notch filter enable
+        uint8_t odr : 8;           // output data rate
+    } sensorSettings;
+
+    static_assert(sizeof(sensorSettings) == 2,
+                  "sensorSettings size is not 2 bytes");
+
+    struct ND015DDataExtended
+    {
+        uint16_t pressure;
+        uint16_t temperature;
+        char model[8];
+        uint8_t serial[4];
+        uint8_t build[6];
+    };
+
+    PrintLogger logger = Logging::getLogger("nd015d");
+};
+
+}  // namespace Boardcore
+
diff --git a/src/shared/sensors/ND015X/ND015XData.h b/src/shared/sensors/ND015X/ND015XData.h
new file mode 100644
index 0000000000000000000000000000000000000000..d7d4cdd426caeaac61e6b553ad1d996faa8b57c5
--- /dev/null
+++ b/src/shared/sensors/ND015X/ND015XData.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Pietro Bortolus
+ *
+ * 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 ND015XData : public PressureData
+{
+    static std::string header() { return "timestamp,pressure\n"; }
+
+    void print(std::ostream& os) const
+    {
+        os << pressureTimestamp << "," << pressure << "\n";
+    }
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/utils/Constants.h b/src/shared/utils/Constants.h
index 5caf126047883890839c97eeb829c49aa69767b0..b2a8c6ca551a0ed6ca89e50a9e18aa5c8956b465 100644
--- a/src/shared/utils/Constants.h
+++ b/src/shared/utils/Constants.h
@@ -31,6 +31,7 @@ namespace Constants
 static constexpr float PI                 = 3.14159265f;  // [rad]
 static constexpr float DEGREES_TO_RADIANS = PI / 180.0f;
 static constexpr float RADIANS_TO_DEGREES = 180.0f / PI;
+static constexpr float PSI_TO_PASCAL      = 6894.76f;  // [Pa / psi]
 
 static constexpr float g = 9.80665f;  // [m^s^2]
 
diff --git a/src/tests/sensors/test-nd015x.cpp b/src/tests/sensors/test-nd015x.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca715c17bfeada626c45a0c7d5892056a5c79522
--- /dev/null
+++ b/src/tests/sensors/test-nd015x.cpp
@@ -0,0 +1,89 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Pietro Bortolus
+ *
+ * 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 <miosix.h>
+
+#include <iostream>
+
+#include "sensors/ND015X/ND015A.h"
+#include "sensors/ND015X/ND015D.h"
+
+using namespace miosix;
+using namespace Boardcore;
+
+// Pin definitions for Lyra Biscotto
+GpioPin sckPin      = GpioPin(GPIOE_BASE, 2);
+GpioPin misoPin     = GpioPin(GPIOE_BASE, 5);
+GpioPin mosiPin     = GpioPin(GPIOE_BASE, 6);
+GpioPin csPinND015A = GpioPin(GPIOB_BASE, 9);
+GpioPin csPinND015D = GpioPin(GPIOB_BASE, 8);
+
+void initPins()
+{
+    // Setup gpio pins
+
+    sckPin.alternateFunction(5);
+    sckPin.mode(Mode::ALTERNATE);
+    misoPin.alternateFunction(5);
+    misoPin.mode(Mode::ALTERNATE);
+    mosiPin.alternateFunction(5);
+    mosiPin.mode(Mode::ALTERNATE);
+    csPinND015A.mode(Mode::OUTPUT);
+    csPinND015A.high();
+    csPinND015D.mode(Mode::OUTPUT);
+    csPinND015D.high();
+}
+
+int main()
+{
+    // Initialize SPI pins
+    initPins();
+
+    SPIBus bus(SPI4);
+
+    ND015A sensor(bus, csPinND015A, ND015A::getDefaultSPIConfig());
+    // ND015D sensor(bus, csPinND015D, ND015D::getDefaultSPIConfig());
+    ND015XData sensorData;
+
+    sensor.init();
+
+    if (sensor.checkModelMatch())
+    {
+        std::cout << "Sensor initialized correctly" << std::endl;
+    }
+    else
+    {
+        std::cout << "Sensor failed to initialize" << std::endl;
+        return 1;
+    }
+
+    while (true)
+    {
+        sensor.sample();
+        sensorData = sensor.getLastSample();
+        std::cout << "New data: " << sensorData.pressure << std::endl;
+
+        Thread::sleep(1000);
+    }
+
+    return 0;
+}