diff --git a/CMakeLists.txt b/CMakeLists.txt
index ea116b5211996b039464de1848fedba46988f799..8e5162364a0b16ffce590db067bfe8abc5401265 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -232,6 +232,9 @@ sbs_target(test-calibration stm32f407vg_stm32f4discovery)
 add_executable(test-bme280 src/tests/sensors/test-bme280.cpp)
 sbs_target(test-bme280 stm32f429zi_stm32f4discovery)
 
+add_executable(test-bmp280 src/tests/sensors/test-bmp280.cpp)
+sbs_target(test-bmp280 stm32f429zi_stm32f4discovery)
+
 add_executable(test-bmx160 src/tests/sensors/test-bmx160.cpp)
 sbs_target(test-bmx160 stm32f429zi_skyward_death_stack_x)
 
diff --git a/cmake/boardcore.cmake b/cmake/boardcore.cmake
index 979aa24005d368b63bb645051dc14523a4164089..9d99de5a811713715c005a7a24a917ecdb1ff5ae 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -68,6 +68,7 @@ foreach(OPT_BOARD ${BOARDS})
         ${SBS_BASE}/src/shared/sensors/ADS131M04/ADS131M04.cpp
         ${SBS_BASE}/src/shared/sensors/ADS131M04/ADS131M04HighFreq.cpp
         ${SBS_BASE}/src/shared/sensors/BME280/BME280.cpp
+        ${SBS_BASE}/src/shared/sensors/BMP280/BMP280.cpp
         ${SBS_BASE}/src/shared/sensors/BMX160/BMX160.cpp
         ${SBS_BASE}/src/shared/sensors/BMX160/BMX160WithCorrection.cpp
         ${SBS_BASE}/src/shared/sensors/calibration/SensorDataExtra.cpp
diff --git a/src/shared/sensors/BME280/BME280.cpp b/src/shared/sensors/BME280/BME280.cpp
index 907b92432b0c724c122ba60085ff714e262c37d6..4701793dfe05f765013c3a2a672a9f2276933808 100644
--- a/src/shared/sensors/BME280/BME280.cpp
+++ b/src/shared/sensors/BME280/BME280.cpp
@@ -54,16 +54,6 @@ BME280::BME280(SPISlave spiSlave_, BME280Config config_)
 
 bool BME280::init()
 {
-    // Check if already initialized
-    if (initialized)
-    {
-        LOG_ERR(logger, "Already initialized");
-
-        lastError = SensorErrors::ALREADY_INIT;
-
-        return false;
-    }
-
     // Check WHO AM I
     if (!checkWhoAmI())
     {
@@ -99,20 +89,19 @@ bool BME280::init()
         return false;
     }
 
-    initialized = true;
     return true;
 }
 
-void BME280::setHumidityOversampling(Oversampling oversampling)
+void BME280::setSensorMode(Mode mode)
 {
-    config.bits.oversamplingHumidity = oversampling;
+    config.bits.mode = mode;
 
     setConfiguration();
 }
 
-void BME280::setSensorMode(Mode mode)
+void BME280::setHumidityOversampling(Oversampling oversampling)
 {
-    config.bits.mode = mode;
+    config.bits.oversamplingHumidity = oversampling;
 
     setConfiguration();
 }
@@ -215,12 +204,6 @@ TemperatureData BME280::readTemperature()
     return lastSample;
 }
 
-HumidityData BME280::getHumidity() { return lastSample; }
-
-PressureData BME280::getPressure() { return lastSample; }
-
-TemperatureData BME280::getTemperature() { return lastSample; }
-
 unsigned int BME280::calculateMaxMeasurementTime(BME280Config config_)
 {
     return ceil(1.25 + (2.3 * config_.bits.oversamplingTemperature) +
@@ -286,6 +269,8 @@ bool BME280::checkWhoAmI()
 
     uint8_t whoAmIValue = transaction.readRegister(REG_ID);
 
+    printf("%2X\n", whoAmIValue);
+
     return whoAmIValue == REG_ID_VAL;
 }
 
diff --git a/src/shared/sensors/BME280/BME280.h b/src/shared/sensors/BME280/BME280.h
index 3dd66cce83dc94db771992ce508560981cdfe1a7..7ab2c6cdd39e4d1973091a4c4c40d24e546369db 100644
--- a/src/shared/sensors/BME280/BME280.h
+++ b/src/shared/sensors/BME280/BME280.h
@@ -163,12 +163,6 @@ public:
      */
     bool init() override;
 
-    /**
-     * @brief Sets the oversampling for humidity readings, use SKIPPED to
-     * disable humidity sampling
-     */
-    void setHumidityOversampling(Oversampling oversampling);
-
     /**
      * @brief Sets the sensor mode
      *
@@ -182,6 +176,12 @@ public:
      */
     void setSensorMode(Mode mode);
 
+    /**
+     * @brief Sets the oversampling for humidity readings, use SKIPPED to
+     * disable humidity sampling
+     */
+    void setHumidityOversampling(Oversampling oversampling);
+
     /**
      * @brief Sets the oversampling for pressure readings, use SKIPPED to
      * disable pressure sampling
@@ -220,12 +220,6 @@ public:
      */
     TemperatureData readTemperature();
 
-    HumidityData getHumidity();
-
-    PressureData getPressure();
-
-    TemperatureData getTemperature();
-
     /**
      * @brief Maximum measurement time formula from datasheet page 51
      *
@@ -301,8 +295,6 @@ private:
     BME280Comp compParams;
     int32_t fineTemperature;  // Used in compensation algorithm
 
-    bool initialized = false;  // Whether the sensor has been initialized
-
     PrintLogger logger = Logging::getLogger("bme280");
 };
 
diff --git a/src/shared/sensors/BME280/BME280Data.h b/src/shared/sensors/BME280/BME280Data.h
index 4cfe0e90794942102f34641687ff22763e9d557d..473989e103fe62904a75f1c13a1ee425102f5e4f 100644
--- a/src/shared/sensors/BME280/BME280Data.h
+++ b/src/shared/sensors/BME280/BME280Data.h
@@ -46,9 +46,8 @@ struct BME280Data : public TemperatureData,
 
     static std::string header()
     {
-        return "temperatureTimestamp,temp,pressureTimestamp,press,humid_"
-               "timestamp,"
-               "humid\n";
+        return "temperatureTimestamp,temperature,pressureTimestamp,pressure,"
+               "humid_timestamp,humidity\n";
     }
 
     void print(std::ostream& os) const
diff --git a/src/shared/sensors/BMP280/BMP280.cpp b/src/shared/sensors/BMP280/BMP280.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..12a1f15b75f826078d5f7dc11a35270d80cd6368
--- /dev/null
+++ b/src/shared/sensors/BMP280/BMP280.cpp
@@ -0,0 +1,315 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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 "BMP280.h"
+
+#include <drivers/timer/TimestampTimer.h>
+#include <math.h>
+
+using namespace std;
+
+namespace Boardcore
+{
+
+const BMP280::BMP280Config BMP280::BMP280_DEFAULT_CONFIG = {
+    0, 0, SLEEP_MODE, SKIPPED, SKIPPED, 0, FILTER_OFF, STB_TIME_0_5};
+
+const BMP280::BMP280Config BMP280::BMP280_CONFIG_ALL_ENABLED = {0,
+                                                                0,
+                                                                NORMAL_MODE,
+                                                                OVERSAMPLING_16,
+                                                                OVERSAMPLING_2,
+                                                                0,
+                                                                FILTER_COEFF_16,
+                                                                STB_TIME_0_5};
+
+const BMP280::BMP280Config BMP280::BMP280_CONFIG_TEMP_SINGLE = {
+    0, 0, FORCED_MODE, SKIPPED, OVERSAMPLING_1, 0, FILTER_OFF, STB_TIME_0_5};
+
+BMP280::BMP280(SPISlave spiSlave_, BMP280Config config_)
+    : spiSlave(spiSlave_), config(config_)
+{
+}
+
+bool BMP280::init()
+{
+    // Check WHO AM I
+    if (!checkWhoAmI())
+    {
+        LOG_ERR(logger, "Invalid WHO AM I");
+
+        lastError = SensorErrors::INVALID_WHOAMI;
+
+        return false;
+    }
+
+    loadCompensationParameters();
+
+    // Read once the temperature to compute fineTemperature
+    setConfiguration(BMP280_CONFIG_TEMP_SINGLE);
+    miosix::Thread::sleep(
+        calculateMaxMeasurementTime(BMP280_CONFIG_TEMP_SINGLE));
+    readTemperature();
+
+    setConfiguration();
+
+    BMP280Config readBackConfig = readConfiguration();
+
+    // Check if the configration on the device matches ours
+    if (config.bytes.ctrlPressureAndTemperature !=
+            readBackConfig.bytes.ctrlPressureAndTemperature ||
+        config.bytes.config != readBackConfig.bytes.config)
+    {
+        LOG_ERR(logger, "Device configuration incorrect, setup failed");
+
+        lastError = SensorErrors::NOT_INIT;
+
+        return false;
+    }
+
+    return true;
+}
+
+void BMP280::setSensorMode(Mode mode)
+{
+    config.bits.mode = mode;
+
+    setConfiguration();
+}
+
+void BMP280::setPressureOversampling(Oversampling oversampling)
+{
+    config.bits.oversamplingPressure = oversampling;
+
+    setConfiguration();
+}
+
+void BMP280::setTemperatureOversampling(Oversampling oversampling)
+{
+    config.bits.oversamplingTemperature = oversampling;
+
+    setConfiguration();
+}
+
+void BMP280::setFilterCoeff(FilterCoeff filterCoeff)
+{
+    config.bits.filter = filterCoeff;
+
+    setConfiguration();
+}
+
+void BMP280::setStandbyTime(StandbyTime standbyTime)
+{
+    config.bits.standbyTime = standbyTime;
+
+    setConfiguration();
+}
+
+PressureData BMP280::readPressure()
+{
+    uint8_t buffer[3];
+    int32_t adc_P = 0;
+
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.readRegisters(REG_PRESS_MSB, buffer, 3);
+    }
+
+    adc_P |= ((uint32_t)buffer[0]) << 12;
+    adc_P |= ((uint32_t)buffer[1]) << 4;
+    adc_P |= (buffer[2] >> 4) & 0x0F;
+
+    // Compensate pressure
+    lastSample.pressureTimestamp = TimestampTimer::getInstance().getTimestamp();
+    lastSample.pressure =
+        (float)compensatePressure(adc_P) / 256;  // Convert to Pa
+
+    return lastSample;
+}
+
+TemperatureData BMP280::readTemperature()
+{
+    uint8_t buffer[3];
+    int32_t adcTemperature = 0;
+
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.readRegisters(REG_TEMP_MSB, buffer, 3);
+    }
+
+    adcTemperature |= ((uint32_t)buffer[0]) << 12;
+    adcTemperature |= ((uint32_t)buffer[1]) << 4;
+    adcTemperature |= (buffer[2] >> 4) & 0x0F;
+
+    // Compensate temperature
+    fineTemperature = computeFineTemperature(adcTemperature);
+    lastSample.temperatureTimestamp =
+        TimestampTimer::getInstance().getTimestamp();
+    lastSample.temperature = (float)compensateTemperature(fineTemperature) /
+                             100;  // Converto to DegC
+
+    return lastSample;
+}
+
+unsigned int BMP280::calculateMaxMeasurementTime(BMP280Config config_)
+{
+    // TODO: This folrmula is not present in the BMP280's datasheet, it should
+    // be checked
+    return ceil(1.25 + (2.3 * config_.bits.oversamplingTemperature) +
+                (2.3 * config_.bits.oversamplingPressure + 0.575));
+}
+
+unsigned int BMP280::getMaxMeasurementTime()
+{
+    return calculateMaxMeasurementTime(config);
+}
+
+bool BMP280::selfTest() { return checkWhoAmI(); }
+
+BMP280Data BMP280::sampleImpl()
+{
+    uint8_t buffer[8];
+    int32_t adcTemperature = 0;
+    int32_t adc_P          = 0;
+    int32_t adc_H          = 0;
+    BMP280Data data;
+
+    // TODO: implement selective read!
+
+    // Burst read pressure, temperature and humidity
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.readRegisters(REG_PRESS_MSB, buffer, 8);
+    }
+
+    adcTemperature |= ((uint32_t)buffer[3]) << 12;
+    adcTemperature |= ((uint32_t)buffer[4]) << 4;
+    adcTemperature |= (buffer[5] >> 4) & 0x0F;
+
+    adc_P |= ((uint32_t)buffer[0]) << 12;
+    adc_P |= ((uint32_t)buffer[1]) << 4;
+    adc_P |= (buffer[2] >> 4) & 0x0F;
+
+    adc_H |= ((uint32_t)buffer[6] << 8);
+    adc_H |= buffer[7];
+
+    // Compensate temperature
+    fineTemperature           = computeFineTemperature(adcTemperature);
+    data.temperatureTimestamp = TimestampTimer::getInstance().getTimestamp();
+    data.temperature          = (float)compensateTemperature(fineTemperature) /
+                       100;  // Converto to DegC
+
+    // Compensate pressure
+    data.pressureTimestamp = TimestampTimer::getInstance().getTimestamp();
+    data.pressure = (float)compensatePressure(adc_P) / 256;  // Convert to Pa
+
+    return data;
+}
+
+bool BMP280::checkWhoAmI()
+{
+    SPITransaction transaction(spiSlave);
+
+    uint8_t whoAmIValue = transaction.readRegister(REG_ID);
+
+    printf("%2X\n", whoAmIValue);
+
+    return whoAmIValue == REG_ID_VAL;
+}
+
+void BMP280::setConfiguration() { setConfiguration(config); }
+
+void BMP280::setConfiguration(BMP280Config config_)
+{
+    SPITransaction transaction(spiSlave);
+
+    transaction.writeRegister(REG_CONFIG & 0x7F, config_.bytes.config);
+    transaction.writeRegister(REG_CTRL_MEAS & 0x7F,
+                              config_.bytes.ctrlPressureAndTemperature);
+}
+
+BMP280::BMP280Config BMP280::readConfiguration()
+{
+    BMP280Config tmp;
+    SPITransaction transaction(spiSlave);
+
+    transaction.readRegisters(REG_STATUS, (uint8_t *)&tmp, 3);
+
+    return tmp;
+}
+
+void BMP280::loadCompensationParameters()
+{
+    // Read compensation parameters
+    {
+        SPITransaction transaction(spiSlave);
+
+        transaction.readRegisters(REG_CALIB_0, (uint8_t *)&compParams, 25);
+    }
+}
+
+int32_t BMP280::computeFineTemperature(int32_t adcTemperature)
+{
+    int32_t var1, var2;
+    var1 = ((((adcTemperature >> 3) - ((int32_t)compParams.bits.dig_T1 << 1))) *
+            ((int32_t)compParams.bits.dig_T2)) >>
+           11;
+    var2 = (((((adcTemperature >> 4) - ((int32_t)compParams.bits.dig_T1)) *
+              ((adcTemperature >> 4) - ((int32_t)compParams.bits.dig_T1))) >>
+             12) *
+            ((int32_t)compParams.bits.dig_T3)) >>
+           14;
+    return var1 + var2;
+}
+
+int32_t BMP280::compensateTemperature(int32_t fineTemperature)
+{
+    return (fineTemperature * 5 + 128) >> 8;
+}
+
+uint32_t BMP280::compensatePressure(int32_t adc_P)
+{
+    int64_t var1, var2, p;
+    var1 = ((int64_t)fineTemperature) - 128000;
+    var2 = var1 * var1 * (int64_t)compParams.bits.dig_P6;
+    var2 = var2 + ((var1 * (int64_t)compParams.bits.dig_P5) << 17);
+    var2 = var2 + (((int64_t)compParams.bits.dig_P4) << 35);
+    var1 = ((var1 * var1 * (int64_t)compParams.bits.dig_P3) >> 8) +
+           ((var1 * ((int64_t)compParams.bits.dig_P2) << 12));
+    var1 =
+        ((((int64_t)1) << 47) + var1) * ((int64_t)compParams.bits.dig_P1) >> 33;
+    if (var1 == 0)
+    {
+        return 0;  // avoid exception caused by division by zero
+    }
+    p    = 1048576 - adc_P;
+    p    = (((p << 31) - var2) * 3125) / var1;
+    var1 = (((int64_t)compParams.bits.dig_P9) * (p >> 13) * (p >> 13)) >> 25;
+    var2 = (((int64_t)compParams.bits.dig_P8) * p) >> 19;
+    p    = ((p + var1 + var2) >> 8) + (((int64_t)compParams.bits.dig_P7) << 4);
+    return (uint32_t)p;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/BMP280/BMP280.h b/src/shared/sensors/BMP280/BMP280.h
new file mode 100644
index 0000000000000000000000000000000000000000..385a40695fc386cc647cd051431198c150f9b835
--- /dev/null
+++ b/src/shared/sensors/BMP280/BMP280.h
@@ -0,0 +1,273 @@
+/* Copyright (c) 2022 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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 "BMP280Data.h"
+
+namespace Boardcore
+{
+
+class BMP280 : public Sensor<BMP280Data>
+{
+public:
+    enum Oversampling
+    {
+        SKIPPED         = 0x0,  ///< Skipped (output set to 0x8000)
+        OVERSAMPLING_1  = 0x1,  ///< Oversampling x1
+        OVERSAMPLING_2  = 0x2,  ///< Oversampling x2
+        OVERSAMPLING_4  = 0x3,  ///< Oversampling x4
+        OVERSAMPLING_8  = 0x4,  ///< Oversampling x8
+        OVERSAMPLING_16 = 0x5,  ///< Oversampling x16
+    };
+
+    enum Mode
+    {
+        SLEEP_MODE  = 0x0,  ///< Sleep mode
+        FORCED_MODE = 0x1,  ///< Forced mode
+        NORMAL_MODE = 0x3   ///< Normal mode
+    };
+
+    enum StandbyTime
+    {
+        STB_TIME_0_5  = 0x0,  ///< 0.5 ms
+        STB_TIME_62_5 = 0x1,  ///< 62.5 ms
+        STB_TIME_125  = 0x2,  ///< 125 ms
+        STB_TIME_250  = 0x3,  ///< 250 ms
+        STB_TIME_500  = 0x4,  ///< 500 ms
+        STB_TIME_1000 = 0x5,  ///< 1000 ms
+        STB_TIME_20   = 0x6,  ///< 20 ms
+        STB_TIME_40   = 0x7   ///< 40 ms
+    };
+
+    enum FilterCoeff
+    {
+        FILTER_OFF      = 0x0,  ///< Filter off
+        FILTER_COEFF_2  = 0x1,  ///< Filter coefficient = 2
+        FILTER_COEFF_4  = 0x2,  ///< Filter coefficient = 4
+        FILTER_COEFF_8  = 0x3,  ///< Filter coefficient = 8
+        FILTER_COEFF_16 = 0x4   ///< Filter coefficient = 16
+    };
+
+    union BMP280Config
+    {
+        struct __attribute__((packed)) BMP280ConfigBits
+        {
+            // status
+            /**
+             * '1' when the NVM data are being copied to image registers, '0'
+             * when the copying is done
+             */
+            uint8_t imUpdate : 1;
+            uint8_t : 2;
+            /**
+             * '1' whenever a conversion is running, '0' when the result have
+             * been transferred to the data registers
+             */
+            uint8_t measuring : 1;
+            uint8_t : 4;
+
+            Mode mode : 2;  ///< Device modes
+            Oversampling
+                oversamplingPressure : 3;  ///< Oversampling of pressure
+            Oversampling
+                oversamplingTemperature : 3;  ///< Oversampling of temperature
+
+            // config
+            uint8_t spi3wEn : 1;  ///< Enables 3-wire SPI interface
+            uint8_t : 1;
+            FilterCoeff filter : 3;       ///< Time constant of the IIR filter
+            StandbyTime standbyTime : 3;  ///< Inactive duration in normal mode
+        } bits;
+
+        struct
+        {
+            uint8_t status;                      ///< Device status
+            uint8_t ctrlPressureAndTemperature;  ///< Pressure and temperature
+                                                 ///< options
+            uint8_t config;  ///< Rate, filter and interface options
+        } bytes;
+
+        uint8_t bytesArray[3];
+    };
+
+    union BMP280Comp
+    {
+        struct __attribute__((packed))
+        {
+            uint16_t dig_T1;
+            int16_t dig_T2;
+            int16_t dig_T3;
+            uint16_t dig_P1;
+            int16_t dig_P2;
+            int16_t dig_P3;
+            int16_t dig_P4;
+            int16_t dig_P5;
+            int16_t dig_P6;
+            int16_t dig_P7;
+            int16_t dig_P8;
+            int16_t dig_P9;
+        } bits;
+
+        uint8_t bytesArray[24];
+    };
+
+    static constexpr uint8_t REG_ID_VAL = 0x58;  ///< Who am I value
+
+    static const BMP280Config BMP280_DEFAULT_CONFIG;      ///< Default register
+                                                          ///< values
+    static const BMP280Config BMP280_CONFIG_ALL_ENABLED;  ///< Datasheet
+                                                          ///< values for
+                                                          ///< indoor
+                                                          ///< navigation
+    static const BMP280Config BMP280_CONFIG_TEMP_SINGLE;  ///< Temperature
+                                                          ///< enabled in
+                                                          ///< forced mode
+
+    explicit BMP280(SPISlave spiSlave_,
+                    BMP280Config config_ = BMP280_CONFIG_ALL_ENABLED);
+
+    /**
+     * @brief Initialize the device with the specified configuration
+     */
+    bool init() override;
+
+    /**
+     * @brief Sets the sensor mode
+     *
+     * Values:
+     * - SLEEP_MODE: No measurements are performed
+     * - FORCED_MODE: A single measurement is performed when this function
+     * writes the configuration, after the measurement the sensor return to
+     * sleep mode
+     * - NORMAL_MODE: Automated cycling between measurements, standby time can
+     * be set using setStandbyTime()
+     */
+    void setSensorMode(Mode mode);
+
+    /**
+     * @brief Sets the oversampling for pressure readings, use SKIPPED to
+     * disable pressure sampling
+     */
+    void setPressureOversampling(Oversampling oversampling);
+
+    /**
+     * @brief Sets the oversampling for temperature readings, use SKIPPED to
+     * disable temperature sampling
+     */
+    void setTemperatureOversampling(Oversampling oversampling);
+
+    /**
+     * @brief Sets the coefficient for the IIR filter (applied to temperature
+     * and pressure)
+     */
+    void setFilterCoeff(FilterCoeff filterCoeff);
+
+    /**
+     * @brief Sets the standby time between readings in normal mode
+     */
+    void setStandbyTime(StandbyTime standbyTime);
+
+    /**
+     * @brief Reads only the pressure, does not set the configuration
+     */
+    PressureData readPressure();
+
+    /**
+     * @brief Reads only the temperature, does not set the configuration
+     */
+    TemperatureData readTemperature();
+
+    /**
+     * @brief Maximum measurement time formula from datasheet page 51
+     *
+     * @return Time in milliseconds
+     */
+    static unsigned int calculateMaxMeasurementTime(BMP280Config config_);
+
+    unsigned int getMaxMeasurementTime();
+
+    /**
+     * @brief Reads the WHO AM I register
+     *
+     * @return True if everything ok
+     */
+    bool selfTest() override;
+
+private:
+    BMP280Data sampleImpl() override;
+
+    void setConfiguration();
+
+    void setConfiguration(BMP280Config config_);
+
+    BMP280Config readConfiguration();
+
+    void loadCompensationParameters();
+
+    // Compensation algorithm rev.1.1 from Bosh datasheet
+
+    int32_t computeFineTemperature(int32_t adcTemperature);
+
+    int32_t compensateTemperature(int32_t fineTemperature);
+
+    uint32_t compensatePressure(int32_t adcPressure);
+    /**
+     * @brief Check the WHO AM I code from the device.
+     *
+     * @return true if the device is recognized
+     */
+    bool checkWhoAmI();
+
+    enum BMP280Registers : uint8_t
+    {
+        REG_CALIB_0 = 0x88,
+        // Calibration register 1-25
+
+        REG_ID    = 0xD0,
+        REG_RESET = 0xE0,
+
+        REG_STATUS    = 0xF3,
+        REG_CTRL_MEAS = 0xF4,
+        REG_CONFIG    = 0xF5,
+
+        REG_PRESS_MSB  = 0xF7,
+        REG_PRESS_LSB  = 0xF8,
+        REG_PRESS_XLSB = 0xF9,
+        REG_TEMP_MSB   = 0xFA,
+        REG_TEMP_LSB   = 0xFB,
+        REG_TEMP_XLSB  = 0xFC
+    };
+
+    const SPISlave spiSlave;
+    BMP280Config config;
+    BMP280Comp compParams;
+    int32_t fineTemperature;  // Used in compensation algorithm
+
+    PrintLogger logger = Logging::getLogger("bmp280");
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/sensors/BMP280/BMP280Data.h b/src/shared/sensors/BMP280/BMP280Data.h
new file mode 100644
index 0000000000000000000000000000000000000000..cfd3cecc200fb8be00b137bc725a6a62a2776f45
--- /dev/null
+++ b/src/shared/sensors/BMP280/BMP280Data.h
@@ -0,0 +1,55 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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 BMP280Data : public TemperatureData, public PressureData
+{
+    BMP280Data() : TemperatureData{0, 0.0}, PressureData{0, 0.0} {}
+
+    BMP280Data(uint64_t timestamp, float temperature, float pressure,
+               float humidity)
+        : TemperatureData{timestamp, temperature}, PressureData{timestamp,
+                                                                pressure}
+
+    {
+    }
+
+    static std::string header()
+    {
+        return "temperatureTimestamp,temperature,pressureTimestamp,pressure,\n";
+    }
+
+    void print(std::ostream& os) const
+    {
+        os << temperatureTimestamp << "," << temperature << ","
+           << pressureTimestamp << "," << pressure << ","
+           << "\n";
+    }
+};
+
+}  // namespace Boardcore
diff --git a/src/tests/sensors/test-bme280.cpp b/src/tests/sensors/test-bme280.cpp
index ab960d91ffcd84e51929f4deb927f596b4fd6875..afa223a4a46bab257afa859e1f4fd0ea1fad2cf4 100644
--- a/src/tests/sensors/test-bme280.cpp
+++ b/src/tests/sensors/test-bme280.cpp
@@ -54,15 +54,15 @@ GpioPin csPin   = GpioPin(GPIOC_BASE, 1);
 void initBoard()
 {
     // Alternate function configuration for SPI pins
-    sckPin.mode(miosix::Mode::ALTERNATE);
+    sckPin.mode(Mode::ALTERNATE);
     sckPin.alternateFunction(5);  // SPI function
-    mosiPin.mode(miosix::Mode::ALTERNATE);
+    mosiPin.mode(Mode::ALTERNATE);
     mosiPin.alternateFunction(5);  // SPI function
-    misoPin.mode(miosix::Mode::ALTERNATE);
+    misoPin.mode(Mode::ALTERNATE);
     misoPin.alternateFunction(5);  // SPI function
 
     // Chip select pin as output starting high
-    csPin.mode(miosix::Mode::OUTPUT);
+    csPin.mode(Mode::OUTPUT);
     csPin.high();
 }
 
@@ -98,7 +98,7 @@ int main()
     {
         bme280.setSensorMode(BME280::FORCED_MODE);
 
-        miosix::Thread::sleep(bme280.getMaxMeasurementTime());
+        Thread::sleep(bme280.getMaxMeasurementTime());
 
         bme280.sample();
 
@@ -106,7 +106,7 @@ int main()
               bme280.getLastSample().temperature,
               bme280.getLastSample().pressure, bme280.getLastSample().humidity);
 
-        miosix::Thread::sleep(1000);
+        Thread::sleep(1000);
     }
 
     TRACE("Normal mode\n");
@@ -119,6 +119,6 @@ int main()
               bme280.getLastSample().temperature,
               bme280.getLastSample().pressure, bme280.getLastSample().humidity);
 
-        miosix::Thread::sleep(50);  // 25Hz
+        Thread::sleep(50);  // 25Hz
     }
 }
diff --git a/src/tests/sensors/test-bmp280.cpp b/src/tests/sensors/test-bmp280.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d83478dede299492b607e2434a70c35b559b0906
--- /dev/null
+++ b/src/tests/sensors/test-bmp280.cpp
@@ -0,0 +1,107 @@
+/* Copyright (c) 2021 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio
+ *
+ * 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/BMP280/BMP280.h>
+#include <utils/Debug.h>
+
+using namespace miosix;
+using namespace Boardcore;
+
+GpioPin sckPin  = GpioPin(GPIOA_BASE, 5);
+GpioPin misoPin = GpioPin(GPIOB_BASE, 4);
+GpioPin mosiPin = GpioPin(GPIOA_BASE, 7);
+GpioPin csPin   = GpioPin(GPIOB_BASE, 2);
+
+void initBoard()
+{
+    // Alternate function configuration for SPI pins
+    sckPin.mode(Mode::ALTERNATE);
+    sckPin.alternateFunction(5);  // SPI function
+    mosiPin.mode(Mode::ALTERNATE);
+    mosiPin.alternateFunction(5);  // SPI function
+    misoPin.mode(Mode::ALTERNATE);
+    misoPin.alternateFunction(5);  // SPI function
+
+    // Chip select pin as output starting high
+    csPin.mode(Mode::OUTPUT);
+    csPin.high();
+}
+
+int main()
+{
+    // Enable SPI clock and set gpios
+    initBoard();
+
+    // SPI configuration setup
+    SPIBusConfig spiConfig;
+    // spiConfig.clockDivider = SPI::ClockDivider::DIV_32;
+    spiConfig.mode = SPI::Mode::MODE_0;
+    SPIBus spiBus(SPI1);
+    SPISlave spiSlave(spiBus, csPin, spiConfig);
+
+    // Device initialization
+    BMP280 bmp280(spiSlave);
+
+    bmp280.init();
+
+    // In practice the self test reads the who am i reagister, this is already
+    // done in init()
+    if (!bmp280.selfTest())
+    {
+        TRACE("Self test failed!\n");
+
+        return -1;
+    }
+
+    // Try forced mode
+    TRACE("Forced mode\n");
+    for (int i = 0; i < 10; i++)
+    {
+        bmp280.setSensorMode(BMP280::FORCED_MODE);
+
+        Thread::sleep(bmp280.getMaxMeasurementTime());
+
+        bmp280.sample();
+
+        TRACE("temp: %.2f DegC\tpress: %.2f hPa\n",
+              bmp280.getLastSample().temperature,
+              bmp280.getLastSample().pressure);
+
+        Thread::sleep(1000);
+    }
+
+    TRACE("Normal mode\n");
+    bmp280.setSensorMode(BMP280::NORMAL_MODE);
+    while (true)
+    {
+        bmp280.sample();
+
+        TRACE("temp: %.2f DegC\tpress: %.2f Pa\thumid: %.2f %%RH\n",
+              bmp280.getLastSample().temperature,
+              bmp280.getLastSample().pressure);
+
+        Thread::sleep(50);  // 25Hz
+    }
+}