diff --git a/.gitignore b/.gitignore
index 1f62fb87aeac8e9a7e9cfb4572dc49a2a4bee886..be91b21147e8842750043ac7982987bb86bfb2d4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,3 +26,4 @@ build
 
 store.json
 scripts/homeone/event_header_generator/generated/
+core
diff --git a/sbs.conf b/sbs.conf
index 58769fb570789f0dd4485b02f5f62889560abdaf..1bc18d5bd1b64c225b4da538fc7a34a931d2e05d 100644
--- a/sbs.conf
+++ b/sbs.conf
@@ -114,6 +114,11 @@ Files:      src/shared/scheduler/TaskScheduler.cpp
 Type:       srcfiles
 Files:      src/shared/drivers/pwm/pwm.cpp
 
+[spi]
+Type:       srcfiles
+Files:      src/shared/drivers/spi/SPIBus.cpp
+            src/shared/drivers/spi/SPITransaction.cpp
+
 [i2c]
 Type:       srcfiles
 Files:      src/shared/drivers/i2c/stm32f2_f4_i2c.cpp
@@ -159,12 +164,16 @@ Files:      src/shared/utils/testutils/TestHelper.cpp
 
 [tests]
 Type:       srcfiles
-Files:      src/tests/catch/test-eventbroker.cpp
-            src/tests/catch/test-circularbuffer.cpp
-            src/tests/catch/test-aero.cpp
+Files:      src/tests/catch/test-aero.cpp
             src/tests/catch/test-buttonhandler.cpp
+            src/tests/catch/test-circularbuffer.cpp
+            src/tests/catch/test-eventbroker.cpp
+            src/tests/catch/test-hardwaretimer.cpp
+            #src/tests/catch/test-kalman.cpp
+            src/tests/catch/test-matrix.cpp
             src/tests/catch/test-packetqueue.cpp
-
+            src/tests/catch/spidriver/test-spidriver
+            
 
 #-------------------------------#
 #          Entrypoints          #
@@ -207,9 +216,9 @@ Main:       kernel-testsuite
 
 [tests-catch]
 Type:       test
-BoardId:    stm32f429zi_skyward_homeone
+BoardId:    stm32f429zi_stm32f4discovery
 BinName:    tests-catch
-Include:    %tests %shared %test-utils
+Include:    %tests %shared %test-utils %spi
 Defines:
 Main:       catch/catch-tests-entry
 
@@ -271,6 +280,7 @@ Main:       test-pinobserver
 
 ## Drivers
 
+
 [test-dsgamma]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
@@ -303,14 +313,6 @@ Include:    %shared
 Defines:
 Main:       drivers/test-lsm
 
-[test-timer]
-Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-timer
-Include:    %shared
-Defines:    -DDEBUG
-Main:       drivers/test-timer
-
 [test-canbus]
 Type:       test
 BoardId:    stm32f429zi_stm32f4discovery
@@ -363,7 +365,7 @@ Main:       drivers/test-mavlink
 Type:       test
 BoardId:    stm32f429zi_skyward_death_stack
 BinName:    xbee-bitrate
-Include:    %shared %xbee
+Include:    %shared %xbee %spi
 Defines:   -DDEBUG -DSDRAM_ISSI
 Main:       misc/xbee-bitrate
 
@@ -371,7 +373,7 @@ Main:       misc/xbee-bitrate
 Type:       test
 BoardId:    stm32f429zi_skyward_death_stack
 BinName:    xbee-send-rcv
-Include:    %shared %xbee
+Include:    %shared %xbee %spi
 Defines:    -DDEBUG
 Main:       misc/xbee-send-rcv
 
@@ -379,7 +381,7 @@ Main:       misc/xbee-send-rcv
 Type:       test
 BoardId:    stm32f429zi_skyward_death_stack
 BinName:    xbee-time-to-send
-Include:    %shared %xbee
+Include:    %shared %xbee %spi
 Defines:    -DDEBUG
 Main:       misc/xbee-time-to-send
 
@@ -391,21 +393,14 @@ Include:    %shared
 Defines:
 Main:       drivers/test-ad7994
 
-[test-matrix]
-Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-matrix
-Include:    %shared
-Defines:    -DSTANDALONE_CATCH1_TEST
-Main:       catch/test-matrix
 
-[test-kalman]
-Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-kalman
-Include:    %shared
-Defines:    -DSTANDALONE_CATCH1_TEST
-Main:       catch/test-kalman
+# [test-circularbuffer]
+# Type:       test
+# BoardId:    stm32f429zi_stm32f4discovery
+# BinName:    test-circularbuffer
+# Include:    %shared  %test-utils 
+# Defines:    -DSTANDALONE_CATCH1_TEST
+# Main:       catch/test-circularbuffer
 
 #[test-eventbroker]
 #Type:       test
@@ -415,13 +410,6 @@ Main:       catch/test-kalman
 #Defines:    -DSTANDALONE_CATCH1_TEST
 #Main:       catch/test-eventbroker
 
-[test-circularbuffer]
-Type:       test
-BoardId:    stm32f429zi_stm32f4discovery
-BinName:    test-circularbuffer
-Include:    %shared
-Defines:    -DSTANDALONE_CATCH1_TEST
-Main:       catch/test-circularbuffer
 
 [test-tempSensor]
 Type:       test
@@ -475,10 +463,18 @@ Main:       drivers/calibrate-mpu9250
 Type:       test
 BoardId:    stm32f429zi_skyward_death_stack
 BinName:    test-ms5803
-Include:    %shared
+Include:    %shared %spi
 Defines:
 Main:       drivers/test-ms5803
 
+[test-l3gd20]
+Type:       test
+BoardId:    stm32f429zi_stm32f4discovery
+BinName:    test-l3gd20
+Include:    %shared %spi
+Defines:
+Main:       drivers/test-l3gd20
+
 [test-rls]
 Type:       test
 BoardId:    stm32f429zi_skyward_death_stack
diff --git a/src/shared/drivers/HardwareTimer.h b/src/shared/drivers/HardwareTimer.h
index f983b78a4d7245b062aa6281a5f497bbd4c72d63..cb82f77b63815471b8cd60cb673be7ec53b65afa 100644
--- a/src/shared/drivers/HardwareTimer.h
+++ b/src/shared/drivers/HardwareTimer.h
@@ -1,5 +1,5 @@
-/*
- * Copyright (c) 2018 Skyward Experimental Rocketry
+/**
+ * Copyright (c) 2018-2019 Skyward Experimental Rocketry
  * Authors: Luca Erbetta
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
@@ -14,26 +14,18 @@
  *
  * 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
+ * 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.
  */
 
-#ifndef SRC_SHARED_DRIVERS_HARDWARETIMER_H
-#define SRC_SHARED_DRIVERS_HARDWARETIMER_H
+#pragma once
 
-#include <kernel/scheduler/scheduler.h>
 #include <miosix.h>
-#include <cassert>
-#include "Debug.h"
 
-#if !defined _ARCH_CORTEXM4_STM32F4
-#error "Unsupported architecture!"
-#endif
-
-using miosix::FastInterruptDisableLock;
+#include <type_traits>
 
 class TimerUtils
 {
@@ -91,57 +83,50 @@ public:
     }
 };
 
-template <typename T, unsigned Tim>
+/**
+ * @brief Class for handling Hardware Timers and perform time unit conversions
+ *
+ * @tparam Type type to use to access timer register (uint32_t if 32 bit timer,
+ * uint16_t if 16 bit timer)
+ */
+template <typename Type>
 class HardwareTimer
 {
-    using TimerType = HardwareTimer<T, Tim>;
-
-    static_assert(std::is_same<T, uint32_t>::value ||
-                      std::is_same<T, uint16_t>::value,
-                  "Timer output type must be either uint16_t or uint32_t");
-
-    static_assert(!((Tim == 2 || Tim == 5) && std::is_same<T, uint16_t>::value),
-                  "Tim2 and Tim5 are 32 bit timers!");
+    static_assert(std::is_same<Type, uint32_t>::value ||
+                      std::is_same<Type, uint16_t>::value,
+                  "Type must be either uint32_t or uint16_t.");
 
 public:
-    inline static TimerType& instance()
-    {
-        static TimerType timer;
-        return timer;
-    }
-
-    inline T start()
+    /**
+     * @brief Creates a new HardwareTimer instance
+     *
+     * @param    timer     The timer to use (pointer to timer registers struct)
+     * @param    psc_input_freq Input frequency of the timer's prescaler, see
+     *                          TimerUtils::getPrescalerInputFrequency()
+     */
+    HardwareTimer(TIM_TypeDef* timer, unsigned int psc_input_freq)
+        : tim(timer), prescaler_freq(psc_input_freq)
     {
-        if (!ticking)
-        {
-            ticking  = true;
-            TIM->CNT = 0;  // Reset the counter
-
-            TIM->CR1 |= TIM_CR1_CEN;
-            return 0;
-        }
-        else
-        {
-            return tick();
-        }
     }
 
-    inline T tick() { return TIM->CNT; }
-
-    inline T stop()
-    {
-        if (ticking)
-        {
-            T tick = TIM->CNT;
-            TIM->CR1 &= ~TIM_CR1_CEN;
-            ticking = false;
-            return tick;
-        }
+    /**
+     * @brief Starts the timer returns the current tick
+     *
+     * @return Current tick
+     */
+    Type start();
 
-        return 0;
-    }
+    /**
+     * @brief Stops the timer if already started
+     *
+     * @return Current tick or 0 if already stopped
+     */
+    Type stop();
 
-    bool isTicking() { return ticking; }
+    /**
+     * @brief Returns current tick
+     */
+    Type tick();
 
     /**
      * @brief Converts from ticks to microseconds, using the current prescaler
@@ -150,11 +135,7 @@ public:
      * @param ticks
      * @return float
      */
-    float toMicroSeconds(T ticks)
-    {
-        return (1.0f * ticks * 1000000 * (1 + prescaler)) /
-               (float)TimerUtils::getPrescalerInputFrequency(clk);
-    }
+    float toMicroSeconds(Type ticks);
 
     /**
      * @brief Converts from ticks to milliseconds, using the current prescaler
@@ -163,11 +144,7 @@ public:
      * @param ticks
      * @return float
      */
-    float toMilliSeconds(T ticks)
-    {
-        return (1.0f * ticks * 1000 * (1 + prescaler)) /
-               (float)TimerUtils::getPrescalerInputFrequency(clk);
-    }
+    float toMilliSeconds(Type ticks);
 
     /**
      * @brief Converts from ticks to seconds, using the current prescaler
@@ -176,154 +153,129 @@ public:
      * @param ticks
      * @return float
      */
-    float toSeconds(T ticks)
-    {
-        return (1.0f * ticks * (1 + prescaler)) /
-               (float)TimerUtils::getPrescalerInputFrequency(clk);
-    }
+    float toSeconds(Type ticks);
 
-    /*static float toSeconds(T tick) {}
-    static float toMilliseconds(T tick) {}
-    static float toMicroSeconds(T tick) {}*/
+    /**
+     * @brief Returns the current resolution of the timer in microseconds.
+     * @return Resolution in microseconds
+     */
+    float getResolution();
+
+    /**
+     * @brief Returns the maximum time the timer can measure before restarting
+     * from 0, in seconds.
+     *
+     * @return Maximum timer duration in seconds.
+     */
+    float getMaxDuration();
 
     /**
      * @brief Sets the prescaler value.
      * The tick frequency is defined as the clock frequency value of the timer
      * divided by the prescaler. See the datasheet for further information.
      */
-    void setPrescaler(uint16_t prescaler)
-    {
-        // reset();
-        TIM->PSC = prescaler;
-        TIM->EGR =
-            TIM_EGR_UG;  // Send an update event to load the new prescaler
-        // value
-        this->prescaler = prescaler;
-    }
+    void setPrescaler(uint16_t prescaler);
 
     /**
      * @brief Set the auto reload value.
      * The auto reload value is the maximum value the timer can count to.
      * After this value is reached, the timers counts back from 0.
      */
-    void setAutoReload(T auto_reload)
-    {
-        if (ticking)
-        {
-            stop();
-        }
-        this->auto_reload = auto_reload;
-        TIM->ARR          = auto_reload;
-
-        TIM->EGR =
-            TIM_EGR_UG;  // Send an update event to load the new auto-reload
-    }
+    void setAutoReload(Type auto_reload);
 
 private:
-    HardwareTimer()
+    TIM_TypeDef* tim;
+    unsigned prescaler_freq;
+
+    bool ticking = false;
+    uint16_t prescaler = 0;
+    Type auto_reload =
+        static_cast<Type>(-1);  // Max value of Type (Type is unsigned)
+};
+
+template <typename Type>
+inline Type HardwareTimer<Type>::start()
+{
+    if (!ticking)
     {
-        switch (Tim)
-        {
-            case 1:
-                TIM_EN = RCC_APB2ENR_TIM1EN;
-                TIM    = TIM1;
-                break;
-            default:  // Use TIM2 as default
-                TRACE("Wrong timer selected. Using TIM2.\n");
-            case 2:
-                TIM_EN = RCC_APB1ENR_TIM2EN;
-                TIM    = TIM2;
-                break;
-            case 3:
-                TIM_EN = RCC_APB1ENR_TIM3EN;
-                TIM    = TIM3;
-                break;
-            case 4:
-                TIM_EN = RCC_APB1ENR_TIM4EN;
-                TIM    = TIM4;
-                break;
-            case 5:
-                TIM_EN = RCC_APB1ENR_TIM5EN;
-                TIM    = TIM5;
-                break;
-            case 6:
-                TIM_EN = RCC_APB1ENR_TIM6EN;
-                TIM    = TIM6;
-                break;
-            case 7:
-                TIM_EN = RCC_APB1ENR_TIM7EN;
-                TIM    = TIM7;
-                break;
-            case 8:
-                TIM_EN = RCC_APB2ENR_TIM8EN;
-                TIM    = TIM8;
-                break;
-            case 9:
-                TIM_EN = RCC_APB2ENR_TIM9EN;
-                TIM    = TIM9;
-                break;
-            case 10:
-                TIM_EN = RCC_APB2ENR_TIM10EN;
-                TIM    = TIM10;
-                break;
-            case 11:
-                TIM_EN = RCC_APB2ENR_TIM11EN;
-                TIM    = TIM11;
-                break;
-            case 12:
-                TIM_EN = RCC_APB1ENR_TIM12EN;
-                TIM    = TIM12;
-                break;
-            case 13:
-                TIM_EN = RCC_APB1ENR_TIM13EN;
-                TIM    = TIM13;
-                break;
-            case 14:
-                TIM_EN = RCC_APB1ENR_TIM14EN;
-                TIM    = TIM14;
-                break;
-        }
+        ticking  = true;
+        tim->CNT = 0;  // Reset the counter
 
-        if (Tim == 1 || (Tim >= 8 && Tim <= 11))
-        {
-            clk = TimerUtils::InputClock::APB2;
+        tim->CR1 |= TIM_CR1_CEN;
+        return 0;
+    }
+    else
+    {
+        return tick();
+    }
+}
 
-            FastInterruptDisableLock dLock;
-            RCC->APB2ENR |= TIM_EN;
-            RCC_SYNC();
-        }
-        else
-        {
-            clk = TimerUtils::InputClock::APB1;
+template <typename Type>
+inline Type HardwareTimer<Type>::tick()
+{
+    return tim->CNT;
+}
 
-            FastInterruptDisableLock dLock;
-            RCC->APB1ENR |= TIM_EN;
-            RCC_SYNC();
-        }
+template <typename Type>
+inline Type HardwareTimer<Type>::stop()
+{
+    if (ticking)
+    {
+        tim->CR1 &= ~TIM_CR1_CEN;
+        ticking = false;
+        return tim->CNT;
+    }
 
-        // Reset control registers
-        TIM->CR1 = 0;
-        TIM->CR2 = 0;
+    return 0;
+}
 
-        auto_reload = static_cast<T>(-1);  // Max value of T (unsigned int)
-        TIM->ARR    = auto_reload;
+template <typename Type>
+void HardwareTimer<Type>::setPrescaler(uint16_t prescaler)
+{
+    this->prescaler = prescaler;
+    tim->PSC        = prescaler;
+    tim->EGR = TIM_EGR_UG;  // Send an update event to load the new prescaler
+                            // value
+}
+
+template <typename Type>
+void HardwareTimer<Type>::setAutoReload(Type auto_reload)
+{
+    this->auto_reload = auto_reload;
+    tim->ARR          = auto_reload;
 
-        prescaler = 0;
-        TIM->PSC  = prescaler;
+    tim->EGR = TIM_EGR_UG;  // Send an update event to load the new auto-reload
+}
 
-        TIM->EGR =
-            TIM_EGR_UG;  // Send an update event to load the new prescaler and
-        // auto reload values
-    }
+template <typename Type>
+float HardwareTimer<Type>::toMicroSeconds(Type ticks)
+{
+    return (1.0f * ticks * 1000000 * (1 + prescaler)) / prescaler_freq;
+}
 
-    TIM_TypeDef* TIM;
-    uint32_t TIM_EN;
-    TimerUtils::InputClock clk;
+template <typename Type>
+float HardwareTimer<Type>::toMilliSeconds(Type ticks)
+{
+    return (1.0f * ticks * 1000 * (1 + prescaler)) / prescaler_freq;
+}
 
-    T auto_reload;
-    uint16_t prescaler;
+template <typename Type>
+float HardwareTimer<Type>::toSeconds(Type ticks)
+{
+    return (1.0f * ticks * (1 + prescaler)) / prescaler_freq;
+}
 
-    bool ticking = false;
-};
+template <typename Type>
+float HardwareTimer<Type>::getResolution()
+{
+    // Resolution in us = number microseconds in one tick
+    return toMicroSeconds(1);
+}
 
-#endif /* SRC_SHARED_DRIVERS_HARDWARETIMER_H */
+template <typename Type>
+float HardwareTimer<Type>::getMaxDuration()
+{
+    // Maximum duration = number of seconds to count to the auto_reload (reset)
+    // value.
+    return toSeconds(auto_reload);
+}
\ No newline at end of file
diff --git a/src/shared/drivers/Xbee/Xbee.h b/src/shared/drivers/Xbee/Xbee.h
index 163089b0b7745512d289c034ce17ecca519c0f5e..d269b1e27052a71a807ac8571cc865dd792cad03 100644
--- a/src/shared/drivers/Xbee/Xbee.h
+++ b/src/shared/drivers/Xbee/Xbee.h
@@ -23,12 +23,13 @@
 #pragma once
 
 #include <Common.h>
-#include <drivers/BusTemplate.h>
 #include <drivers/Transceiver.h>
+#include <drivers/spi/SPIDriver.h>
+#include <miosix.h>
+
 #include <algorithm>
 #include <vector>
 
-#include <miosix.h>
 #include "ActiveObject.h"
 #include "XbeeStatus.h"
 #include "diagnostic/StackLogger.h"
@@ -106,25 +107,51 @@ static void __attribute__((used)) handleATTNInterrupt()
 }
 
 /**
- * WARNING: An IRQ linked with the ATTN pin of the Xbee module must be enabled
+ * @warning: An IRQ linked with the ATTN pin of the Xbee module must be enabled
  * before using this class. See test/misc/xbee-bitrate for an example.
+ * @warning: Due to the way this driver is written, the SPI bus must be reserved
+ * only for the XBee device. No other devices can communicate on the same bus.
  */
-template <typename Bus, class CS, class ATTN, class RST>
 class Xbee : public Transceiver, public ActiveObject
 {
 public:
-    Xbee(unsigned int send_timeout) : send_timeout(send_timeout)
+    Xbee(SPIBusInterface& bus, GpioPin cs, GpioPin attn, GpioPin rst,
+         unsigned int send_timeout = 1000)
+        : send_timeout(send_timeout), spi_xbee(bus, cs), attn(attn), rst(rst)
     {
+        spi_xbee.config.br = SPIBaudRate::DIV_128;
+
+        // No need to configure before each transaction, we are the only device
+        // on the bus.
+        spi_xbee.bus.configure(spi_xbee.config);
+
         reset();
         miosix::Thread::sleep(10);
 
-        CS::low();
+        spi_xbee.cs.low();
         miosix::Thread::sleep(1);
-        CS::high();
+        spi_xbee.cs.high();
+        miosix::Thread::sleep(1);
+    }
+
+    Xbee(SPIBusInterface& bus, GpioPin cs, GpioPin attn, GpioPin rst,
+         SPIBusConfig spi_config, unsigned int send_timeout = 1000)
+        : send_timeout(send_timeout), spi_xbee(bus, cs, spi_config), attn(attn),
+          rst(rst)
+    {
+        // No need to configure before each transaction, we are the only device
+        // on the bus.
+        spi_xbee.bus.configure(spi_xbee.config);
+
+        reset();
+        miosix::Thread::sleep(10);
+
+        spi_xbee.cs.low();
+        miosix::Thread::sleep(1);
+        spi_xbee.cs.high();
         miosix::Thread::sleep(1);
     }
 
-    Xbee() : Xbee(1000) {}
     /*
      * Send a message through the XBee
      * Blocks until the message is sent (successfully or not)
@@ -231,7 +258,7 @@ protected:
 
                 // Check if we have data to send (tx_buf.size > 0) or receive
                 // (attn == 0) with disabled interrupts to avoid race conditions
-                if (ATTN::value() != 0 && tx_buf.size() == 0)
+                if (attn.value() != 0 && tx_buf.size() == 0)
                 {
                     // If we have nothing to receive or send, wait.
                     waiting = miosix::Thread::getCurrentThread();
@@ -292,10 +319,10 @@ private:
 
     void reset()
     {
-        RST::mode(miosix::Mode::OPEN_DRAIN);
-        RST::low();
+        rst.mode(miosix::Mode::OPEN_DRAIN);
+        rst.low();
         miosix::delayUs(500);
-        RST::high();
+        rst.high();
     }
 
     /**
@@ -319,9 +346,12 @@ private:
      */
     void transferData()
     {
+        SPIBusInterface& bus = spi_xbee.bus;
+
         ParseResult result = ParseResult::IDLE;
 
-        CS::low();
+        bus.select(spi_xbee.cs);
+
         vector<uint8_t> data;
 
         {
@@ -338,7 +368,7 @@ private:
         {
             // Full duplex transfer, the data vector is replaced with received
             // data, if any.
-            Bus::transfer(data.data(), data.size());
+            bus.transfer(data.data(), data.size());
 
             // Parse the received data
             for (uint8_t rx : data)
@@ -355,7 +385,7 @@ private:
             // If there's nothing more to parse, return
             if (result != ParseResult::PARSING)
             {
-                CS::high();
+                bus.deselect(spi_xbee.cs);
                 return;
             }
 
@@ -365,7 +395,7 @@ private:
         // Read until we have received a packet (or no packet is found)
         do
         {
-            result = parse(Bus::read());
+            result = parse(bus.read());
         } while (result == ParseResult::PARSING);
 
         if (result == ParseResult::SUCCESS)
@@ -377,7 +407,7 @@ private:
             TRACE("[Xbee] Read failed. Parser result: %d\n", (int)result);
         }
 
-        CS::high();
+        bus.deselect(spi_xbee.cs);
     }
 
     /**
@@ -610,6 +640,10 @@ private:
     vector<uint8_t> parser_buf;
 
     XbeeStatus status;
+
+    SPISlave spi_xbee;
+    GpioPin attn;
+    GpioPin rst;
 };  // namespace Xbee
 
 }  // namespace Xbee
\ No newline at end of file
diff --git a/src/shared/drivers/spi/MockSPIBus.h b/src/shared/drivers/spi/MockSPIBus.h
new file mode 100644
index 0000000000000000000000000000000000000000..5bbcb07737e5ea0b15cd99d06ecf0c4b528a2f40
--- /dev/null
+++ b/src/shared/drivers/spi/MockSPIBus.h
@@ -0,0 +1,239 @@
+/**
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ *
+ * 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 <cstdint>
+#include <vector>
+
+#include "SPIInterface.h"
+
+using std::vector;
+
+/**
+ * @brief Mock SPI Bus to be used for testing communication to a single slave:
+ * data are read and written to two buffers on the memory that can then be
+ * checked. Operations on the mock bus will not be successfull if the
+ * configuration is not correct or the "slave" is not selected.
+ *
+ * Usage:
+ * 1. Set the expected config (data wont be written / read if the bus current
+ * configuration of the bus is different from the expected one)
+ * 2. Set the data to be read from the bus (in_buf).
+ * 3. Perform operations. write() will write bytes in out_buf, read() will
+ * return data from in_buf.
+ * 4. Check if out_buf contains the expected data. Check if data returned from
+ * read() is as expected from in_buf.
+ * 5. ???
+ * 6. Profit.
+ */
+class MockSPIBus : public SPIBusInterface
+{
+public:
+    MockSPIBus() {}
+    ~MockSPIBus() {}
+
+    // Delete copy/move contructors/operators
+    MockSPIBus(const MockSPIBus&) = delete;
+    MockSPIBus& operator=(const MockSPIBus&) = delete;
+
+    MockSPIBus(MockSPIBus&&) = delete;
+    MockSPIBus& operator=(MockSPIBus&&) = delete;
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t byte) override;
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    uint8_t read() override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    void read(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    uint8_t transfer(uint8_t data) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    void transfer(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::select()
+     *
+     * @param cs Not used, pass any GpioPin
+     */
+    void select(GpioPin& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::deselect()
+     *
+     * @param cs Not used, pass any GpioPin
+     */
+    void deselect(GpioPin& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::configure()
+     */
+    void configure(SPIBusConfig config) override;
+
+    /**
+     * @brief Wether the chip select is asserted or not
+     */
+    bool isSelected() { return selected; }
+
+    vector<uint8_t> out_buf;  // Data written on the bus are stored here
+    vector<uint8_t> in_buf;   // Store here data to be read from the bus
+
+    unsigned int in_buf_pos = 0;  // Read data iterator
+
+    SPIBusConfig expected_config;  // Expected configuration of the bus
+
+private:
+    bool canCommunicate();
+
+    SPIBusConfig current_config;
+    bool selected = false;
+};
+
+bool MockSPIBus::canCommunicate()
+{
+    return selected && current_config == expected_config;
+}
+
+void MockSPIBus::write(uint8_t byte)
+{
+    if (canCommunicate())
+    {
+        out_buf.push_back(byte);
+    }
+    else
+    {
+        out_buf.push_back(0);
+    }
+}
+
+void MockSPIBus::write(uint8_t* data, size_t size)
+{
+    if (canCommunicate())
+    {
+        out_buf.insert(out_buf.end(), data, data + size);
+    }
+    else
+    {
+        out_buf.insert(out_buf.end(), size, 0);
+    }
+}
+
+uint8_t MockSPIBus::read()
+{
+    if (canCommunicate())
+    {
+        return in_buf[in_buf_pos++];
+    }
+    return 0;
+}
+
+void MockSPIBus::read(uint8_t* data, size_t size)
+{
+    if (canCommunicate())
+    {
+        for (size_t i = 0; i < size; i++)
+        {
+            *data = in_buf[in_buf_pos++];
+            data++;
+        }
+    }
+    else
+    {
+        for (size_t i = 0; i < size; i++)
+        {
+            *data = 0;
+            data++;
+        }
+    }
+}
+
+uint8_t MockSPIBus::transfer(uint8_t data)
+{
+    if (canCommunicate())
+    {
+
+        out_buf.push_back(data);
+        return in_buf[in_buf_pos++];
+    }
+    else
+    {
+
+        out_buf.push_back(0);
+        return 0;
+    }
+}
+
+void MockSPIBus::transfer(uint8_t* data, size_t size)
+{
+    if (canCommunicate())
+    {
+        for (size_t i = 0; i < size; i++)
+        {
+            out_buf.push_back(*data);
+            *data = in_buf[in_buf_pos++];
+            data++;
+        }
+    }
+    else
+    {
+        for (size_t i = 0; i < size; i++)
+        {
+            out_buf.push_back(0);
+            *data = 0;
+            data++;
+        }
+    }
+}
+
+void MockSPIBus::select(GpioPin& cs)
+{
+    (void)cs;
+    selected = true;
+}
+
+void MockSPIBus::deselect(GpioPin& cs)
+{
+    (void)cs;
+    selected = false;
+}
+
+void MockSPIBus::configure(SPIBusConfig config) { current_config = config; }
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SPIBus.cpp b/src/shared/drivers/spi/SPIBus.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c7dc7c803a4972a0150f1b9ac6d916f63ec6af67
--- /dev/null
+++ b/src/shared/drivers/spi/SPIBus.cpp
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ *
+ * 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 "SPIBus.h"
+
+SPIBus::SPIBus(SPI_TypeDef* spi) : spi(spi) {}
+
+void SPIBus::configure(SPIBusConfig new_config)
+{
+    // Reconfigure the bus only if config enabled. Do not reconfigure if already
+    // in the correct configuration.
+    if (config_enabled && (!first_config_applied || new_config != config))
+    {
+        first_config_applied = true;
+        config               = new_config;
+
+        // Clean CR1
+        spi->CR1 = 0;
+
+        // Configure clock division (BR bits)
+        spi->CR1 |= (static_cast<uint16_t>(config.br) & 0x0007) << 3;
+        // Configure CPOL & CPHA bits
+        spi->CR1 |= ((uint16_t)config.cpol & 0x0001) << 1 |
+                    ((uint16_t)config.cpha & 0x0001);
+
+        // Configure LSBFIRST bit
+        spi->CR1 |= (uint16_t)config.lsb_first << 7;
+
+        spi->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM  // Use software chip-select
+                    | SPI_CR1_MSTR             // Master mode
+                    | SPI_CR1_SPE;             // Enable SPI
+    }
+}
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SPIBus.h b/src/shared/drivers/spi/SPIBus.h
new file mode 100644
index 0000000000000000000000000000000000000000..eb02b313e4aa7fb938287342ecc0d12f2e39085b
--- /dev/null
+++ b/src/shared/drivers/spi/SPIBus.h
@@ -0,0 +1,244 @@
+/**
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ *
+ * 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 "SPIInterface.h"
+
+#pragma once
+
+/**
+ * @brief Low level driver for communicating on a SPI Bus, provides
+ *        SPIBusInterface.
+ */
+class SPIBus : public SPIBusInterface
+{
+public:
+    /**
+     * @brief Instantiates a new SPIBus
+     *
+     * @param spi Pointer to the SPI peripheral to be used
+     */
+    SPIBus(SPI_TypeDef* spi);
+    ~SPIBus() {}
+
+    // Delete copy/move contructors/operators
+    SPIBus(const SPIBus&) = delete;
+    SPIBus& operator=(const SPIBus&) = delete;
+
+    SPIBus(SPIBus&&) = delete;
+    SPIBus& operator=(SPIBus&&) = delete;
+
+    /**
+     * @brief Wether to apply slave-specific bus configuration before each
+     * transaction (BusTemplate compatibility mode).
+     * Only set to false to use SPIDriver alongside BusTemplate.h.
+     * Default value is true.
+     *
+     * @param value True: The slave configuration is applied to the SPI
+     * peripheral before each transaction. False: No configuration is ever
+     * applied to the SPI peripheral. The SPI peripheral retains the
+     * configuration set by BusTemplate.h
+     */
+    void enableSlaveConfiguration(bool value) { config_enabled = value; }
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t byte) override;
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    uint8_t read() override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    void read(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    uint8_t transfer(uint8_t data) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    void transfer(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::select()
+     */
+    void select(GpioPin& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::deselect()
+     */
+    void deselect(GpioPin& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::configure()
+     */
+    void configure(SPIBusConfig config) override;
+
+private:
+    /**
+     * Writes a single byte on the SPI bus.
+     *
+     * @param    byte Pointer to the byte to be written
+     */
+    void write(uint8_t* byte);
+
+    /**
+     * Reads a single byte from the SPI bus.
+     *
+     * @param    byte Pointer to the byte where the read data will be stored
+     */
+    void read(uint8_t* byte);
+
+    /**
+     * Full duplex transfer. Writes a single byte on the SPI bus and replaces
+     * its content with the received data
+     *
+     * @param    byte Pointer to the byte to be transfered
+     */
+    void transfer(uint8_t* byte);
+
+    SPI_TypeDef* spi;
+
+    SPIBusConfig config;
+    bool config_enabled       = true;
+    bool first_config_applied = false;
+};
+
+// Defined here and not in the .cpp to make them inline
+
+inline void SPIBus::write(uint8_t data) { write(&data); }
+
+inline void SPIBus::write(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        write(data + i);
+    }
+}
+
+inline uint8_t SPIBus::read()
+{
+    uint8_t data;
+    read(&data);
+
+    return data;
+}
+
+inline void SPIBus::read(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        read(data + i);
+    }
+}
+
+inline uint8_t SPIBus::transfer(uint8_t data)
+{
+    transfer(&data);
+    return data;
+}
+
+inline void SPIBus::transfer(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        transfer(data + i);
+    }
+}
+
+inline void SPIBus::select(GpioPin& cs)
+{
+    cs.low();
+    if (config.cs_setup_time_us > 0)
+    {
+        delayUs(config.cs_setup_time_us);
+    }
+}
+
+inline void SPIBus::deselect(GpioPin& cs)
+{
+    if (config.cs_hold_time_us > 0)
+    {
+        delayUs(config.cs_hold_time_us);
+    }
+    cs.high();
+}
+
+inline void SPIBus::write(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = *byte;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Clear the RX buffer by accessing the DR register
+    spi->DR;
+}
+
+inline void SPIBus::transfer(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = *byte;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Store the received data in the byte
+    *byte = (uint8_t)spi->DR;
+}
+
+inline void SPIBus::read(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = 0;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Store the received data in the byte
+    *byte = (uint8_t)spi->DR;
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-timer.cpp b/src/shared/drivers/spi/SPIDriver.h
similarity index 64%
rename from src/tests/drivers/test-timer.cpp
rename to src/shared/drivers/spi/SPIDriver.h
index 5ef955e78440eba187000bb61ad56d60a4b22de8..c90ba4743742cfef611f55a0217c44fcd81d46ed 100644
--- a/src/tests/drivers/test-timer.cpp
+++ b/src/shared/drivers/spi/SPIDriver.h
@@ -1,44 +1,28 @@
-/*
- * Copyright (c) 2018 Skyward Experimental Rocketry
- * Authors: Luca Erbetta
- *
+/**
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ * 
  * 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
+ * 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 <cstdio>
-#include "drivers/HardwareTimer.h"
+#pragma once
 
-using miosix::Thread;
-
-int main()
-{
-    HardwareTimer<uint32_t, 2>& timer2 = HardwareTimer<uint32_t, 2>::instance();
-
-    timer2.start();
-
-    while (true)
-    {
-        uint32_t tick = timer2.tick();
-        printf("%lu\t\t(%.3f)\n", tick, timer2.toMilliSeconds(tick));
-
-        usleep(100000);
-    }
-    return 0;
-}
\ No newline at end of file
+#include "SPIInterface.h"
+#include "SPITransaction.h"
+#include "SPIBus.h"
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SPIInterface.h b/src/shared/drivers/spi/SPIInterface.h
new file mode 100644
index 0000000000000000000000000000000000000000..e7d9955e9a5c516e250aa65fdfcd9be740b8fd29
--- /dev/null
+++ b/src/shared/drivers/spi/SPIInterface.h
@@ -0,0 +1,197 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ *
+ * 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 <miosix.h>
+
+#include <cstdint>
+#include <cstdio>
+
+using miosix::delayUs;
+using miosix::GpioPin;
+
+/**
+ * @brief SPI baud rate selection parameter.
+ * SPI clock frequency will be equal to the SPI peripheral bus clock speed
+ * divided by the value specified in this enum.
+ *
+ * Eg: DIV_2 --> spi clock freq = f_PCLK / 2
+ *
+ * See SPI->CR1 on the datasheet for further information.
+ */
+enum class SPIBaudRate
+{
+    DIV_2   = 0,
+    DIV_4   = 1,
+    DIV_8   = 2,
+    DIV_16  = 3,
+    DIV_32  = 4,
+    DIV_64  = 5,
+    DIV_128 = 6,
+    DIV_256 = 7,
+};
+
+/**
+ * @brief SPI Bus configuration for a specific slave.
+ * See slave datasheet for information on how to populate this struct
+ */
+struct SPIBusConfig
+{
+    SPIBaudRate br = SPIBaudRate::DIV_2;  ///> Peripheral clock division
+    uint8_t cpol   = 0;                   ///> Clock polarity (0 - 1)
+    uint8_t cpha   = 0;                   ///> Clock phase (0 - 1)
+    bool lsb_first = false;               ///> MSB or LSB first
+
+    unsigned int cs_setup_time_us = 0;  ///> How long to wait before starting a
+                                        ///> a trasmission after CS is set (us)
+    unsigned int cs_hold_time_us = 0;   ///> How long to hold cs after the end
+                                        ///> of a trasmission (us)
+
+    // Custom comparison operator
+    bool operator==(const SPIBusConfig& other) const
+    {
+        // Compare member-by-member
+        // clang-format off
+        return  br == other.br 
+             && cpol == other.cpol 
+             && cpha == other.cpha 
+             && lsb_first == other.lsb_first 
+             && cs_setup_time_us == other.cs_setup_time_us 
+             && cs_setup_time_us == other.cs_hold_time_us;
+        // clang-format on
+    }
+
+    bool operator!=(const SPIBusConfig& other) const
+    {
+        return !(*this == other);
+    }
+};
+
+/**
+ * @brief Interface for low level access of a SPI bus
+ */
+class SPIBusInterface
+{
+public:
+    SPIBusInterface() {}
+
+    ~SPIBusInterface() {}
+
+    // Delete copy/move contructors/operators
+    SPIBusInterface(const SPIBusInterface&) = delete;
+    SPIBusInterface& operator=(const SPIBusInterface&) = delete;
+
+    SPIBusInterface(SPIBusInterface&&) = delete;
+    SPIBusInterface& operator=(SPIBusInterface&&) = delete;
+
+    /**
+     * @brief Writes a single \p byte to the bus.
+     *
+     * @param    byte Byte to write
+     */
+    virtual void write(uint8_t byte) = 0;
+
+    /**
+     * @brief Writes \p data to the bus.
+     *
+     * @param    data Buffer containing data to write
+     * @param    size Number of bytes to write
+     */
+    virtual void write(uint8_t* data, size_t size) = 0;
+
+    /**
+     * @brief Reads a single byte from the bus.
+     * @return Byte read from the bus
+     */
+    virtual uint8_t read() = 0;
+
+    /**
+     * @brief Reads \p size bytes from the SPI bus, putting them in \p data.
+     *
+     * @param    data Buffer to be filled with received data
+     * @param    size Number of bytes to receive
+     */
+    virtual void read(uint8_t* data, size_t size) = 0;
+
+    /**
+     * @brief Full duplex transmission on the SPI bus.
+     * A \p byte is written on the bus and a byte is read and returned
+     *
+     * @param    byte Byte to write
+     * @return Data read from the bus
+     */
+    virtual uint8_t transfer(uint8_t byte) = 0;
+
+    /**
+     * @brief Full duplex transmission on the SPI bus.
+     * \p data is written on the bus and its contents are then replaced with the
+     * received bytes.
+     *
+     * @param    data Buffer containing data to transfer
+     * @param    size Number of bytes to transfer
+     */
+    virtual void transfer(uint8_t* data, size_t size) = 0;
+
+    /**
+     * @brief Selects the slave
+     *
+     * @param    cs Chip select pin for the slave
+     */
+    virtual void select(GpioPin& cs) = 0;
+
+    /**
+     * @brief Deselects the slave
+     *
+     * @param    cs Chip select pin for the slave
+     * @return
+     */
+    virtual void deselect(GpioPin& cs) = 0;
+
+    /**
+     * @brief Configures the bus with the provided configuration parameters.
+     *
+     * @param    config    Configuration parameters
+     * @return
+     */
+    virtual void configure(SPIBusConfig config) = 0;
+};
+
+/**
+ * @brief Contains information about a single SPI slave device.
+ */
+struct SPISlave
+{
+    SPIBusInterface& bus;  ///> Bus on which the slave is connected
+
+    SPIBusConfig config;  ///> How the bus should be configured to communicate
+                          ///> with the slave.
+    GpioPin cs;           ///> Chip select pin
+
+    SPISlave(SPIBusInterface& bus, GpioPin cs) : bus(bus), cs(cs) {}
+
+    SPISlave(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config)
+        : bus(bus), config(config), cs(cs)
+    {
+    }
+};
diff --git a/src/shared/drivers/spi/SPITransaction.cpp b/src/shared/drivers/spi/SPITransaction.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..8e5746907547e82f25cd8deeaf7f3e9b17a16ca4
--- /dev/null
+++ b/src/shared/drivers/spi/SPITransaction.cpp
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ *
+ * 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 "SPITransaction.h"
+
+SPITransaction::SPITransaction(SPIBusInterface& bus, GpioPin cs,
+                               SPIBusConfig config)
+    : bus(bus), cs(cs)
+{
+    bus.configure(config);
+}
+
+SPITransaction::SPITransaction(SPISlave slave)
+    : SPITransaction(slave.bus, slave.cs, slave.config)
+{
+}
+
+void SPITransaction::write(uint8_t cmd)
+{
+    bus.select(cs);
+    bus.write(&cmd, 1);
+    bus.deselect(cs);
+}
+
+void SPITransaction::write(uint8_t reg, uint8_t val)
+{
+    bus.select(cs);
+    bus.write(&reg, 1);
+    bus.write(&val, 1);
+    bus.deselect(cs);
+}
+
+void SPITransaction::write(uint8_t reg, uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.write(&reg, 1);
+    bus.write(data, size);
+    bus.deselect(cs);
+}
+
+void SPITransaction::write(uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.write(data, size);
+    bus.deselect(cs);
+}
+
+void SPITransaction::transfer(uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.transfer(data, size);
+    bus.deselect(cs);
+}
+
+uint8_t SPITransaction::read(uint8_t reg, bool set_read_bit)
+{
+    if (set_read_bit)
+        reg = reg | 0x80;
+
+    uint8_t out;
+    bus.select(cs);
+    bus.write(&reg, 1);
+    bus.read(&out, 1);
+    bus.deselect(cs);
+    return out;
+}
+
+void SPITransaction::read(uint8_t reg, uint8_t* data, size_t size,
+                          bool set_read_bit)
+{
+    if (set_read_bit)
+        reg = reg | 0x80;
+
+    bus.select(cs);
+    bus.write(&reg, 1);
+    bus.read(data, size);
+    bus.deselect(cs);
+}
+
+void SPITransaction::read(uint8_t* data, size_t size)
+{
+    bus.select(cs);
+    bus.read(data, size);
+    bus.deselect(cs);
+}
\ No newline at end of file
diff --git a/src/shared/drivers/spi/SPITransaction.h b/src/shared/drivers/spi/SPITransaction.h
new file mode 100644
index 0000000000000000000000000000000000000000..2aed6c404cdbae94736c72ed2e86c9ff94fed219
--- /dev/null
+++ b/src/shared/drivers/spi/SPITransaction.h
@@ -0,0 +1,162 @@
+/**
+ * 
+ * 
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ * 
+ * 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 "SPIInterface.h"
+
+/**
+ * @brief Provides high-level access to the SPI Bus for a single transaction.
+ * To make sure the bus is properly configured for the provided slave, you have
+ * to create a new instance of this class for every transaction, as the bus is
+ * configured upon instantiation.
+ *
+ * @warning DO NOT store an instance of this class for later use, as the bus may
+ * be incorrectly configured by then.
+ *
+ * Example transaction:
+ *
+ * {
+ *     // Transaction begin:
+ *     SPITransaction spi(bus, cs, config); // Configures the bus with the
+ *                                          // provided parameters
+ *
+ *     spi.write(REG_EX, 0x56); // writes data to REG_EX
+ *     uint8_t reg2 = spi.read(REG_EX_2); // reads from REG_EX_2
+ *
+ *     // ...As many read/writes as you wish...
+ *
+ *     // transaction end. SPITransaction object is destructed
+ * }
+ */
+class SPITransaction
+{
+public:
+    /**
+     * @brief Instatiates a new SPITransaction, configuring the bus with the
+     * provided parameters
+     *
+     * @param    slave     Slave to communicate with
+     */
+    SPITransaction(SPISlave slave);
+
+    /**
+     * @brief Instatiates a new SPITransaction, configuring the bus with the
+     * provided parameters
+     *
+     * @param    bus       Bus to communicate on
+     * @param    cs        Chip select of the slave to communicate to
+     * @param    config    Configuration of the bus for the selected slave
+     */
+    SPITransaction(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config);
+
+    // Delete copy/move contructors/operators
+    SPITransaction(const SPITransaction&) = delete;
+    SPITransaction& operator=(const SPITransaction&) = delete;
+
+    SPITransaction(SPITransaction&&) = delete;
+    SPITransaction& operator=(SPITransaction&&) = delete;
+
+    /**
+     * @brief Writes a command \p cmd to the bus
+     *
+     * @param    cmd     Command to write on the bus
+     */
+    void write(uint8_t cmd);
+
+    /**
+     * @brief Writes \p val into the \p reg register
+     *
+     * @param    reg     Slave device register
+     * @param    val     Value to be written in the register
+     */
+    void write(uint8_t reg, uint8_t val);
+
+    /**
+     * @brief Writes \p size bytes into the \p reg register
+     *
+     * @param    reg       Slave device register
+     * @param    data      Data to be written
+     * @param    size      Number of bytes to be written
+     */
+    void write(uint8_t reg, uint8_t* data, size_t size);
+
+    /**
+     * @brief Writes \p bytes on the bus
+     *
+     * @param    data      Bytes to be written
+     * @param    size      Number of bytes to be written
+     */
+    void write(uint8_t* data, size_t size);
+
+    /**
+     * @brief Read the contents of the \p reg register
+     *
+     * @param    reg       Slave device register
+     * @param    set_read_bit Wether to set the read bit to 1 (MSB of reg)
+     *                        (default = true).
+     */
+    uint8_t read(uint8_t reg, bool set_read_bit = true);
+
+    /**
+     * @brief Reads \p size bytes from the \p reg register
+     *
+     * @param    reg    Slave device register
+     * @param    data   Buffer where read bytes will be stored
+     * @param    size   Number of bytes to read
+     * @param    set_read_bit Wether to set the read bit to 1 (MSB of reg)
+     *                        (default = true).
+     */
+    void read(uint8_t reg, uint8_t* data, size_t size,
+              bool set_read_bit = true);
+
+    /**
+     * @brief Reads \p size bytes from the bus
+     *
+     * @param    data   Buffer where read bytes will be stored
+     * @param    size   Number of bytes to read
+     */
+    void read(uint8_t* data, size_t size);
+
+    /**
+     * @brief Full duplex transfer: \p data is written on the bus and its
+     *        contents are replaced with the received bytes.
+     *
+     * @param    data      Buffer containign data to be transfered
+     * @param    size      Number of bytes to be transfer
+     */
+    void transfer(uint8_t* data, size_t size);
+
+    /**
+     * @brief Returns the underlying bus for low level access
+     *
+     * @return  SPIBusInterface associated with this transaction
+     */
+    SPIBusInterface& getBus() { return bus; }
+
+private:
+    SPIBusInterface& bus;
+    GpioPin cs;
+};
\ No newline at end of file
diff --git a/src/shared/sensors/L3GD20.h b/src/shared/sensors/L3GD20.h
new file mode 100644
index 0000000000000000000000000000000000000000..9133d5b5090e3fd47ba9257ef8c295c0f5afd1f3
--- /dev/null
+++ b/src/shared/sensors/L3GD20.h
@@ -0,0 +1,238 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ *
+ * 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 <miosix.h>
+
+#include <array>
+
+#include "Sensor.h"
+#include "drivers/spi/SPIDriver.h"
+
+using miosix::GpioPin;
+using std::array;
+
+class L3GD20 : public GyroSensor
+{
+public:
+    enum class FullScaleRange
+    {
+        FS_250  = 250,
+        FS_500  = 500,
+        FS_2000 = 2000
+    };
+
+    enum class OutPutDataRate
+    {
+        ODR_95  = 0x00,
+        ODR_190 = 0x01,
+        ODR_380 = 0x02,
+        ODR_760 = 0x03
+    };
+    /**
+     * @brief Creates an instance of an L3GD20 sensor
+     *
+     * @param    bus SPI bus the sensor is connected to
+     * @param    cs Chip Select GPIO
+     * @param    range Full Scale Range (See datasheet)
+     * @param    odr Output Data Rate (See datasheet)
+     * @param    cutoff_freq Low pass filter cutoff frequency (See datasheet)
+     * @param    fifo_enabled Fifo enabled
+     * @param    fifo_watermark FIFO watermark level in range [1,32] (used for
+     * interrupt generation, see datasheet).
+     */
+    L3GD20(SPIBusInterface& bus, GpioPin cs,
+           FullScaleRange range = FullScaleRange::FS_250,
+           OutPutDataRate odr   = OutPutDataRate::ODR_95,
+           uint8_t cutoff_freq = 0x03, bool fifo_enabled = false,
+           unsigned int fifo_watermark = 24)
+        : fifo_enabled(fifo_enabled), fifo_watermark(fifo_watermark),
+          spislave(bus, cs), fs(range), odr(odr), cutoff_freq(cutoff_freq)
+    {
+        // Configure SPI
+        spislave.config.br = SPIBaudRate::DIV_64;
+        // memset(last_fifo, 0, sizeof(Vec3) * 32);
+    }
+
+    bool init()
+    {
+        SPITransaction spi(spislave);
+
+        uint8_t whoami = spi.read(REG_WHO_AM_I);
+
+        if (whoami != WHO_AM_I_VAL)
+        {
+            printf("WAMI: %d\n", whoami);
+            last_error = ERR_NOT_ME;
+            return false;
+        }
+
+        // uint8_t ctrl4 = spi.read(REG_CTRL4);
+
+        switch (fs)
+        {
+            case FullScaleRange::FS_250:
+                spi.write(REG_CTRL4, 0);
+                break;
+            case FullScaleRange::FS_500:
+                spi.write(REG_CTRL4, (uint8_t)(1 << 4));
+                break;
+            case FullScaleRange::FS_2000:
+                spi.write(REG_CTRL4, (uint8_t)(2 << 4));
+                break;
+            default:
+                break;
+        }
+        if (fifo_enabled)
+        {
+            // Enable fifo
+            spi.write(REG_CTRL5, 1 << 6);
+
+            // Set watermark level to fifo_watermark samples
+            uint8_t fifo_ctrl = fifo_watermark;
+
+            // Set fifo to FIFO mode
+            fifo_ctrl |= 0x02 << 5;
+
+            spi.write(REG_FIFO_CTRL, fifo_ctrl);
+
+            // Enable FIFO watermark interrupt
+            spi.write(REG_CTRL3, 0x04);
+        }
+        // Enter normal mode, enable output
+        uint8_t ctrl1 = 0x0F;
+
+        // Configure ODR
+        ctrl1 |= static_cast<uint8_t>(odr) << 6;
+
+        // Configure cutoff frequency
+        ctrl1 |= (cutoff_freq & 0x03) << 4;
+
+        spi.write(REG_CTRL1, ctrl1);
+
+        return true;
+    }
+
+    bool selfTest() { return true; }
+
+    bool onSimpleUpdate()
+    {
+        float scale = static_cast<int>(fs);
+
+        if (!fifo_enabled)
+        {
+            uint8_t data[6];
+            // Read output data registers (X, Y, Z)
+            {
+                SPITransaction spi(spislave);
+                spi.read(REG_OUT_X_L | 0x40, data, 6);
+            }
+
+            int16_t x = data[0] | data[1] << 8;
+            int16_t y = data[2] | data[3] << 8;
+            int16_t z = data[4] | data[5] << 8;
+            // printf("%02X,%02X,%02X\n", x, y, z);
+
+            mLastGyro =
+                Vec3(x * scale / 65535, y * scale / 65535, z * scale / 65535);
+        }
+        else  // FIFO is enabled
+        {
+            uint8_t buf[192];
+
+            SPITransaction spi(spislave);
+            // Read last fifo level
+            uint8_t fifo_src = spi.read(REG_FIFO_SRC);
+            uint8_t ovr      = (fifo_src & 0x40);
+            last_fifo_level  = fifo_src & 0x1F;
+
+            // if ovr --> fifo is full --> level = level + 1
+            if (ovr)
+            {
+                ++last_fifo_level;
+            }
+
+            // Read fifo
+            spi.read(REG_OUT_X_L | 0x40, buf, last_fifo_level * 6);
+
+            // Convert units & store the FIFO content
+            for (uint8_t i = 0; i < last_fifo_level; i++)
+            {
+                int16_t x = buf[i * 6] | buf[i * 6 + 1] << 8;
+                int16_t y = buf[i * 6 + 2] | buf[i * 6 + 3] << 8;
+                int16_t z = buf[i * 6 + 4] | buf[i * 6 + 5] << 8;
+
+                Vec3 t = Vec3(x * scale / 65535, y * scale / 65535,
+                              z * scale / 65535);
+
+                last_fifo[i] = t;
+            }
+        }
+
+        return true;
+    }
+
+    const array<Vec3, 32>& getLastFifo() const { return last_fifo; }
+    uint8_t getLastFifoSize() const { return last_fifo_level; }
+
+private:
+    bool fifo_enabled = false;
+    unsigned int fifo_watermark;
+
+    array<Vec3, 32> last_fifo;
+    uint8_t last_fifo_level = 0;
+
+    SPISlave spislave;
+    FullScaleRange fs;
+    OutPutDataRate odr;
+    uint8_t cutoff_freq;
+
+    constexpr static uint8_t WHO_AM_I_VAL = 212;
+
+    enum RegMap
+    {
+        REG_WHO_AM_I = 0x0F,
+
+        REG_CTRL1 = 0x20,
+        REG_CTRL2 = 0x21,
+        REG_CTRL3 = 0x22,
+        REG_CTRL4 = 0x23,
+        REG_CTRL5 = 0x24,
+
+        REG_REFERENCE = 0x25,
+        REG_OUT_TEMP  = 0x26,
+        REG_STATUS    = 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,
+
+        REG_FIFO_CTRL = 0x2E,
+        REG_FIFO_SRC  = 0x2F
+    };
+};
\ No newline at end of file
diff --git a/src/shared/sensors/MS580301BA07/MS580301BA07.h b/src/shared/sensors/MS580301BA07/MS580301BA07.h
index b52b1474f8b0e74093602ea0b6517bbf3a3d30ac..5131e32496e1b7e79068ffd5118a72957949daab 100644
--- a/src/shared/sensors/MS580301BA07/MS580301BA07.h
+++ b/src/shared/sensors/MS580301BA07/MS580301BA07.h
@@ -24,19 +24,24 @@
 
 #pragma once
 
-#include <drivers/BusTemplate.h>
 #include "../Sensor.h"
-#include "MS580301BA07Data.h"
 #include "Debug.h"
+#include "MS580301BA07Data.h"
+#include "drivers/spi/SPIDriver.h"
 
-// TODO second order temperature compensation
-template <class Bus>
 class MS580301BA07 : public PressureSensor, public TemperatureSensor
 {
 
 public:
     /* Class constructor. Reset lastPressure and lastTemperature */
-    MS580301BA07()
+    MS580301BA07(SPIBusInterface& bus, GpioPin cs)
+        : MS580301BA07(bus, cs, SPIBusConfig{})
+    {
+        spi_ms5803.config.br = SPIBaudRate::DIV_128;
+    }
+
+    MS580301BA07(SPIBusInterface& bus, GpioPin cs, SPIBusConfig config)
+        : spi_ms5803(bus, cs, config)
     {
         memset(&cd, 0, sizeof(calibration_data));
         mLastTemp     = 0;
@@ -47,15 +52,18 @@ public:
 
     bool init()
     {
+        SPITransaction spi{spi_ms5803};
+
         int timeout = 10;
         do
         {
-            Bus::read(RESET_DEV);
+            spi.read(RESET_DEV);
 
             miosix::Thread::sleep(3);
 
-            cd.sens = readReg(PROM_READ_MASK | PROM_SENS_MASK);
-            if (cd.sens == 0){
+            cd.sens = readReg(spi, PROM_READ_MASK | PROM_SENS_MASK);
+            if (cd.sens == 0)
+            {
                 miosix::Thread::sleep(1);
                 TRACE("Could not read cd.sens\n");
             }
@@ -67,14 +75,14 @@ public:
             return false;
         }
 
-        cd.off      = readReg(PROM_READ_MASK | PROM_OFF_MASK);
-        cd.tcs      = readReg(PROM_READ_MASK | PROM_TCS_MASK);
-        cd.tco      = readReg(PROM_READ_MASK | PROM_TCO_MASK);
-        cd.tref     = readReg(PROM_READ_MASK | PROM_TREF_MASK);
-        cd.tempsens = readReg(PROM_READ_MASK | PROM_TEMPSENS_MASK);
+        cd.off      = readReg(spi, PROM_READ_MASK | PROM_OFF_MASK);
+        cd.tcs      = readReg(spi, PROM_READ_MASK | PROM_TCS_MASK);
+        cd.tco      = readReg(spi, PROM_READ_MASK | PROM_TCO_MASK);
+        cd.tref     = readReg(spi, PROM_READ_MASK | PROM_TREF_MASK);
+        cd.tempsens = readReg(spi, PROM_READ_MASK | PROM_TEMPSENS_MASK);
 
         TRACE("off: %d, tcs: %d, tco: %d, tref: %d, tsens: %d\n", (int)cd.off,
-               (int)cd.tcs, (int)cd.tco, (int)cd.tref, (int)cd.tempsens);
+              (int)cd.tcs, (int)cd.tco, (int)cd.tref, (int)cd.tempsens);
 
         mStatus = 0;
 
@@ -97,34 +105,36 @@ public:
      */
     bool onSimpleUpdate()
     {
+        SPITransaction spi{spi_ms5803}; // Begin an SPI transaction
+
         uint8_t rcvbuf[3];
         uint32_t temperature = 0;
 
         switch (mStatus)
         {
             case STATE_INIT:
-                Bus::write(CONVERT_D1_4096);
+                spi.write(CONVERT_D1_4096);
                 mStatus = STATE_SAMPLED_PRESSURE;
             case STATE_SAMPLED_PRESSURE:
-                Bus::read_low(ADC_READ, rcvbuf, 3);
+                spi.read(ADC_READ, rcvbuf, 3, false);
                 mInternalPressure = rcvbuf[2] | ((uint32_t)rcvbuf[1] << 8) |
                                     ((uint32_t)rcvbuf[0] << 16);
 
-                Bus::write(CONVERT_D2_4096);  // Begin temperature sampling
+                spi.write(CONVERT_D2_4096);  // Begin temperature sampling
                 mStatus = STATE_SAMPLED_TEMPERATURE;
 
                 break;
             case STATE_SAMPLED_TEMPERATURE:
-                Bus::read_low(ADC_READ, rcvbuf, 3);
+                spi.read(ADC_READ, rcvbuf, 3, false);
 
                 // TODO use swapBytes
                 temperature = (uint32_t)rcvbuf[2] | ((uint32_t)rcvbuf[1] << 8) |
                               ((uint32_t)rcvbuf[0] << 16);
 
                 updateData(mInternalPressure, temperature);
-                Bus::write(CONVERT_D1_4096);  // Begin pressure sampling
+                spi.write(CONVERT_D1_4096);  // Begin pressure sampling
                 mStatus = STATE_SAMPLED_PRESSURE;
-            
+
                 break;
         }
 
@@ -143,6 +153,8 @@ public:
     uint8_t getState() { return mStatus; }
 
 private:
+    SPISlave spi_ms5803;
+
     static constexpr uint8_t TIMEOUT = 5;
     uint8_t mStatus;
     uint32_t mInternalPressure;
@@ -205,10 +217,10 @@ private:
 
     calibration_data cd;
 
-    uint16_t readReg(uint8_t reg)
+    uint16_t readReg(SPITransaction& spi, uint8_t reg)
     {
         uint8_t rcv[2];
-        Bus::read(reg, rcv, 2);
+        spi.read(reg, rcv, 2);
         uint16_t data = (rcv[0] << 8) | rcv[1];
         return data;
     }
diff --git a/src/shared/utils/collections/CircularBuffer.h b/src/shared/utils/collections/CircularBuffer.h
index 49b88bf6de8cb535a05fe79fc96ef8ad594d11af..2a4cae1dad4ac949f9ba4edc8dfca51e0efd7b52 100644
--- a/src/shared/utils/collections/CircularBuffer.h
+++ b/src/shared/utils/collections/CircularBuffer.h
@@ -47,7 +47,7 @@ public:
     virtual T& put(const T& elem)
     {
         buffer[write_ptr] = elem;
-        T& added = buffer[write_ptr];
+        T& added          = buffer[write_ptr];
 
         if (!empty && write_ptr == read_ptr)
         {
@@ -62,12 +62,15 @@ public:
 
     /**
      * Gets an element from the buffer, without removing it
-     * Index starts at the element returned by get() or pop(): get(0) is
-     * the same as get()
+     * Index starts from the oldest element in the buffer: get(0) returns the
+     * same element as get()
      *
      * @warning Remember to catch the exception!
+     * @throw range_error if index >= count()
+     *
+     * @param i Index of the elemnt to get, starting from the oldest
+     *
      * @return the element
-     * @throws range_error if buffer is empty
      */
     virtual T& get(unsigned int i)
     {
@@ -81,21 +84,19 @@ public:
     }
 
     /**
-     * @brief Returns the last element added in the buffer
+     * @brief Returns the last element added in the buffer.
+     *
+     * @throw range_error if buffer is empty
      * @warning Remember to catch the exception!
      * @return the element
-     * @throws range_error if buffer is empty
      */
-    virtual T& last()
-    {
-        return get(count() - 1);
-    }
+    virtual T& last() { return get(count() - 1); }
 
     /**
      * Gets the first element from the buffer, without removing it
+     * @throw range_error if buffer is empty
      * @warning Remember to catch the exception!
      * @return the element
-     * @throws range_error if buffer is empty
      */
     virtual T& get()
     {
@@ -109,9 +110,9 @@ public:
 
     /**
      * Pops the first element in the buffer.
+     * @throw range_error if buffer is empty
      * @warning Remember to catch the exception!
      * @return the element that has been popped
-     * @throws range_error if buffer is empty
      */
     virtual const T& pop()
     {
@@ -148,7 +149,10 @@ public:
 
     virtual bool isEmpty() const { return empty; }
 
-    virtual bool isFull() const { return count() == Size; }
+    virtual bool isFull() const
+    {
+        return CircularBuffer<T, Size>::count() == Size;
+    }
     /**
      * Returns the maximum number of elements that can be stored in the buffer
      * @return buffer size
diff --git a/src/tests/catch/spidriver/FakeSPIBus.h b/src/tests/catch/spidriver/FakeSPIBus.h
new file mode 100644
index 0000000000000000000000000000000000000000..2a395552b1fe4d9698a7854154b3d543a270b623
--- /dev/null
+++ b/src/tests/catch/spidriver/FakeSPIBus.h
@@ -0,0 +1,277 @@
+/**
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ *
+ * 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 "FakeSpiTypedef.h"
+#include "drivers/spi/SPIInterface.h"
+
+#pragma once
+
+/**
+ * @brief SPIBus modified to accept a fake Chip select & spi peripheral struct.
+ * Not ideal as modifications to SPIBus have to be manually applied here too,
+ * but that's the only way I found to test it effectively
+ */
+class FakeSPIBus : public SPIBusInterface
+{
+public:
+    /**
+     * @brief Instantiates a new FakeSPIBus
+     *
+     * @param spi Pointer to the SPI peripheral to be used
+     */
+    FakeSPIBus(FakeSpiTypedef* spi);
+    ~FakeSPIBus() {}
+
+    // Delete copy/move contructors/operators
+    FakeSPIBus(const FakeSPIBus&) = delete;
+    FakeSPIBus& operator=(const FakeSPIBus&) = delete;
+
+    FakeSPIBus(FakeSPIBus&&) = delete;
+    FakeSPIBus& operator=(FakeSPIBus&&) = delete;
+
+    /**
+     * @brief Wether to apply slave-specific bus configuration before each
+     * transaction (BusTemplate compatibility mode).
+     * Only set to false to use SPIDriver alongside BusTemplate.h.
+     * Default value is true.
+     *
+     * @param value True: The slave configuration is applied to the SPI
+     * peripheral before each transaction. False: No configuration is ever
+     * applied to the SPI peripheral. The SPI peripheral retains the
+     * configuration set by BusTemplate.h
+     */
+    void enableSlaveConfiguration(bool value) { config_enabled = value; }
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t byte) override;
+
+    /**
+     * @brief See SPIBusInterface::write()
+     */
+    void write(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    uint8_t read() override;
+
+    /**
+     * @brief See SPIBusInterface::read()
+     */
+    void read(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    uint8_t transfer(uint8_t data) override;
+
+    /**
+     * @brief See SPIBusInterface::transfer()
+     */
+    void transfer(uint8_t* data, size_t size) override;
+
+    /**
+     * @brief See SPIBusInterface::select()
+     */
+    void select(GpioPin& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::deselect()
+     */
+    void deselect(GpioPin& cs) override;
+
+    /**
+     * @brief See SPIBusInterface::configure()
+     */
+    void configure(SPIBusConfig config) override;
+
+private:
+    /**
+     * Writes a single byte on the SPI bus.
+     *
+     * @param    byte Pointer to the byte to be written
+     */
+    void write(uint8_t* byte);
+
+    /**
+     * Reads a single byte from the SPI bus.
+     *
+     * @param    byte Pointer to the byte where the read data will be stored
+     */
+    void read(uint8_t* byte);
+
+    /**
+     * Full duplex transfer. Writes a single byte on the SPI bus and replaces
+     * its content with the received data
+     *
+     * @param    byte Pointer to the byte to be transfered
+     */
+    void transfer(uint8_t* byte);
+
+    FakeSpiTypedef* spi;
+
+    SPIBusConfig config;
+    bool config_enabled       = true;
+    bool first_config_applied = false;
+};
+
+FakeSPIBus::FakeSPIBus(FakeSpiTypedef* spi) : spi(spi) {}
+
+void FakeSPIBus::configure(SPIBusConfig new_config)
+{
+    // Reconfigure the bus only if config enabled. Do not reconfigure if already
+    // in the correct configuration.
+    if (config_enabled && (!first_config_applied || new_config != config))
+    {
+        first_config_applied = true;
+        config               = new_config;
+
+        // Clean CR1
+        spi->CR1 = 0;
+
+        // Configure clock division (BR bits)
+        spi->CR1 |= (static_cast<uint16_t>(config.br) & 0x0007) << 3;
+        // Configure CPOL & CPHA bits
+        spi->CR1 |= ((uint16_t)config.cpol & 0x0001) << 1 |
+                    ((uint16_t)config.cpha & 0x0001);
+
+        // Configure LSBFIRST bit
+        spi->CR1 |= (uint16_t)config.lsb_first << 7;
+
+        spi->CR1 |= SPI_CR1_SSI | SPI_CR1_SSM  // Use software chip-select
+                    | SPI_CR1_MSTR             // Master mode
+                    | SPI_CR1_SPE;             // Enable SPI
+    }
+}
+
+inline void FakeSPIBus::write(uint8_t data) { write(&data); }
+
+inline void FakeSPIBus::write(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        write(data + i);
+    }
+}
+
+inline uint8_t FakeSPIBus::read()
+{
+    uint8_t data;
+    read(&data);
+
+    return data;
+}
+
+inline void FakeSPIBus::read(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        read(data + i);
+    }
+}
+
+inline uint8_t FakeSPIBus::transfer(uint8_t data)
+{
+    transfer(&data);
+    return data;
+}
+
+inline void FakeSPIBus::transfer(uint8_t* data, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        transfer(data + i);
+    }
+}
+
+inline void FakeSPIBus::select(GpioPin& rcs)
+{
+    FakeGpioPin& cs = static_cast<FakeGpioPin&>(rcs);
+
+    cs.low();
+    if (config.cs_setup_time_us > 0)
+    {
+        delayUs(config.cs_setup_time_us);
+    }
+}
+
+inline void FakeSPIBus::deselect(GpioPin& rcs)
+{
+    FakeGpioPin& cs = static_cast<FakeGpioPin&>(rcs);
+
+    if (config.cs_hold_time_us > 0)
+    {
+        delayUs(config.cs_hold_time_us);
+    }
+    cs.high();
+}
+
+inline void FakeSPIBus::write(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = *byte;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Clear the RX buffer by accessing the DR register
+    (void)spi->DR;
+}
+
+inline void FakeSPIBus::transfer(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = *byte;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Store the received data in the byte
+    *byte = (uint8_t)spi->DR;
+}
+
+inline void FakeSPIBus::read(uint8_t* byte)
+{
+    // Wait until the peripheral is ready to transmit
+    while ((spi->SR & SPI_SR_TXE) == 0)
+        ;
+    // Write the byte in the transmit buffer
+    spi->DR = 0;
+
+    // Wait until byte is transmitted
+    while ((spi->SR & SPI_SR_RXNE) == 0)
+        ;
+
+    // Store the received data in the byte
+    *byte = (uint8_t)spi->DR;
+}
\ No newline at end of file
diff --git a/src/tests/catch/spidriver/FakeSpiTypedef.h b/src/tests/catch/spidriver/FakeSpiTypedef.h
new file mode 100644
index 0000000000000000000000000000000000000000..dab7a26fa77879e52f1316d44550f0acac447f2c
--- /dev/null
+++ b/src/tests/catch/spidriver/FakeSpiTypedef.h
@@ -0,0 +1,99 @@
+/**
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ *
+ * 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 <miosix.h>
+
+#include <cstdint>
+#include <vector>
+
+using std::vector;
+
+class FakeGpioPin : public miosix::GpioPin
+{
+public:
+    FakeGpioPin() : GpioPin(GPIOA_BASE,1) {}
+
+    void high() { val = 1; }
+
+    void low() { val = 0; }
+
+    int value() { return val; }
+
+private:
+    int val = 1;
+};
+
+/**
+ * @brief Mock STM32F4 SPI peripheral: intercepts register value changes to
+ * emulate a real SPI peripheral / slave.
+ */
+struct FakeSpiTypedef
+{
+    uint32_t CR1 = 0;
+    uint32_t CR2 = 0;
+    uint32_t SR = 3;
+
+    struct RegDR
+    {
+        // Intercept uint32_t assignements
+        void operator=(uint32_t DR)
+        {
+            // If slave is selected & bus configured properly
+            if (parent.cs.value() == 0 && parent.CR1 == parent.CR1_expected &&
+                parent.CR2 == parent.CR2_expected)
+            {
+                out_buf.push_back(DR);
+            }
+        }
+
+        operator uint32_t()
+        {
+            // If slave is selected
+            if (parent.cs.value() == 0 && parent.CR1 == parent.CR1_expected &&
+                parent.CR2 == parent.CR2_expected)
+            {
+                return in_buf[in_it++];
+            }
+
+            return 0;
+        }
+
+        RegDR(FakeSpiTypedef& parent) : parent(parent) {}
+
+        unsigned int in_it = 0;
+        vector<uint32_t> in_buf;
+        vector<uint32_t> out_buf;
+
+    private:
+        FakeSpiTypedef& parent;
+    };
+
+    uint32_t CR1_expected = 0;
+    uint32_t CR2_expected = 0;
+
+    RegDR DR;
+    FakeGpioPin cs;
+
+    FakeSpiTypedef() : DR(*this) {}
+};
\ No newline at end of file
diff --git a/src/tests/catch/spidriver/test-spidriver.cpp b/src/tests/catch/spidriver/test-spidriver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5438d812fa9bbff845e426a997a30dfe08c9e993
--- /dev/null
+++ b/src/tests/catch/spidriver/test-spidriver.cpp
@@ -0,0 +1,477 @@
+/**
+ * Copyright (c) 2020 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta (luca.erbetta@skywarder.eu)
+ *
+ * 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "../catch-tests-entry.cpp"
+#endif
+
+#include <utils/testutils/catch.hpp>
+
+#include "FakeSPIBus.h"
+#include "drivers/spi/MockSPIBus.h"
+#include "drivers/spi/SPIDriver.h"
+
+template <typename T1, typename T2>
+bool bufcmp(T1* buf1, T2* buf2, size_t size)
+{
+    for (size_t i = 0; i < size; i++)
+    {
+        if (*buf1 != *buf2)
+            return false;
+
+        buf1++;
+        buf2++;
+    }
+    return true;
+}
+
+TEST_CASE("SPIBus - Bus Configuration")
+{
+    FakeSpiTypedef spi;
+
+    FakeSPIBus bus{&spi};
+
+    REQUIRE(spi.CR1 == 0);
+
+    SECTION("Configure & check CR1")
+    {
+        SPIBusConfig config;
+        config.br = SPIBaudRate::DIV_16;
+
+        config.cpha      = 1;
+        config.cpol      = 1;
+        config.lsb_first = true;
+
+        uint32_t expected_CR1 = 0x03DF;
+
+        bus.configure(config);
+        REQUIRE(spi.CR1 == expected_CR1);
+
+        // Change config
+        config.br = SPIBaudRate::DIV_256;
+
+        config.cpha      = 0;
+        config.cpol      = 0;
+        config.lsb_first = false;
+
+        expected_CR1 = 0x037C;
+
+        bus.configure(config);
+        REQUIRE(spi.CR1 == expected_CR1);
+
+        config.cpha      = 0;
+        config.cpol      = 1;
+        config.lsb_first = false;
+
+        expected_CR1 = 0x037E;
+
+        bus.configure(config);
+        REQUIRE(spi.CR1 == expected_CR1);
+
+        config.cpha      = 1;
+        config.cpol      = 0;
+        config.lsb_first = false;
+
+        expected_CR1 = 0x037D;
+
+        bus.configure(config);
+        REQUIRE(spi.CR1 == expected_CR1);
+
+        // Reapply same config
+        bus.configure(config);
+        REQUIRE(spi.CR1 == expected_CR1);
+    }
+
+    SECTION("Disable configuration")
+    {
+        SPIBusConfig config;
+        config.br = SPIBaudRate::DIV_16;
+
+        config.cpha      = 1;
+        config.cpol      = 1;
+        config.lsb_first = true;
+
+        bus.enableSlaveConfiguration(false);
+        bus.configure(config);
+        REQUIRE(spi.CR1 == 0);
+    }
+
+    SECTION("Wrong parameters")
+    {
+        SPIBusConfig config;
+        config.br = SPIBaudRate::DIV_16;
+
+        config.cpha      = 8;
+        config.cpol      = 9;
+        config.lsb_first = true;
+
+        uint32_t expected_CR1 = 0x03DE;
+
+        bus.configure(config);
+        REQUIRE(spi.CR1 == expected_CR1);
+    }
+}
+
+TEST_CASE("SPIBus - Chip select")
+{
+    FakeSpiTypedef spi;
+
+    FakeSPIBus bus{&spi};
+
+    REQUIRE(spi.cs.value() == 1);
+
+    bus.select(spi.cs);
+    REQUIRE(spi.cs.value() == 0);
+
+    bus.deselect(spi.cs);
+    REQUIRE(spi.cs.value() == 1);
+}
+
+TEST_CASE("SPIBus - One byte operations")
+{
+    FakeSpiTypedef spi;
+
+    spi.DR.in_buf    = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+    spi.CR1_expected = 0x03DF;
+
+    FakeSPIBus bus{&spi};
+
+    SPIBusConfig config;
+    config.br = SPIBaudRate::DIV_16;
+
+    config.cpha      = 1;
+    config.cpol      = 1;
+    config.lsb_first = true;
+
+    bus.configure(config);
+    bus.select(spi.cs);
+
+    SECTION("Write")
+    {
+        bus.write(1);
+        REQUIRE(spi.DR.out_buf.back() == 1);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+
+        bus.write(2);
+        REQUIRE(spi.DR.out_buf.back() == 2);
+        REQUIRE(spi.DR.out_buf.size() == 2);
+    }
+    SECTION("Read")
+    {
+        REQUIRE(bus.read() == spi.DR.in_buf[0]);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+        REQUIRE(spi.DR.out_buf.back() == 0);
+
+        REQUIRE(bus.read() == spi.DR.in_buf[1]);
+        REQUIRE(spi.DR.out_buf.size() == 2);
+        REQUIRE(spi.DR.out_buf.back() == 0);
+    }
+
+    SECTION("Transfer")
+    {
+        REQUIRE(bus.transfer(55) == spi.DR.in_buf[0]);
+        REQUIRE(spi.DR.out_buf.back() == 55);
+        REQUIRE(spi.DR.out_buf.size() == 1);
+
+        REQUIRE(bus.transfer(255) == spi.DR.in_buf[1]);
+        REQUIRE(spi.DR.out_buf.back() == 255);
+        REQUIRE(spi.DR.out_buf.size() == 2);
+    }
+}
+
+TEST_CASE("SPIBus - Multi byte operations")
+{
+    FakeSpiTypedef spi;
+
+    spi.DR.in_buf    = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+    spi.CR1_expected = 0x03DF;
+
+    FakeSPIBus bus{&spi};
+
+    SPIBusConfig config;
+    config.br = SPIBaudRate::DIV_16;
+
+    config.cpha      = 1;
+    config.cpol      = 1;
+    config.lsb_first = true;
+
+    bus.configure(config);
+    bus.select(spi.cs);
+
+    // 2 identical buffers
+    uint8_t buf[]  = {4, 3, 2, 1};
+    uint8_t bufc[] = {4, 3, 2, 1};
+
+    SECTION("Write")
+    {
+        bus.write(buf, 0);
+        REQUIRE(spi.DR.out_buf.size() == 0);
+
+        bus.write(buf, 4);
+        REQUIRE(spi.DR.out_buf.size() == 4);
+        REQUIRE(bufcmp(bufc, spi.DR.out_buf.data(), 4));
+    }
+
+    SECTION("Read")
+    {
+        bus.read(buf, 0);
+        // Nothing read
+        REQUIRE(bufcmp(bufc, buf, 4));
+
+        bus.read(buf, 4);
+
+        REQUIRE(bufcmp(buf, spi.DR.in_buf.data(), 4));
+    }
+
+    SECTION("Transfer")
+    {
+        bus.transfer(buf, 0);
+        // Nothing read
+        REQUIRE(bufcmp(bufc, buf, 4));
+        // Nothing written
+        REQUIRE(spi.DR.out_buf.size() == 0);
+
+        bus.transfer(buf, 4);
+        REQUIRE(spi.DR.out_buf.size() == 4);
+
+        REQUIRE(bufcmp(bufc, spi.DR.out_buf.data(), 4));
+        REQUIRE(bufcmp(buf, spi.DR.in_buf.data(), 4));
+    }
+}
+
+TEST_CASE("SPITransaction - writes")
+{
+    MockSPIBus bus{};
+    SPIBusConfig config1{};
+
+    config1.cpha = 1;
+    config1.br   = SPIBaudRate::DIV_32;
+
+    bus.expected_config = config1;
+
+    SECTION("Transaction")
+    {
+        SPITransaction spi(bus, GpioPin(GPIOA_BASE, 1), config1);
+
+        REQUIRE(bus.out_buf.size() == 0);
+
+        SECTION("cmd write")
+        {
+            spi.write(9);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.out_buf.size() == 1);
+            REQUIRE(bus.out_buf.back() == 9);
+        }
+
+        SECTION("1 byte reg write")
+        {
+            spi.write(10, 77);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.out_buf.size() == 2);
+            REQUIRE(bus.out_buf[0] == 10);
+            REQUIRE(bus.out_buf[1] == 77);
+        }
+
+        SECTION("multi byte reg write")
+        {
+            uint8_t buf[] = {1, 2, 3, 4, 5, 6};
+
+            SECTION("0 size write")
+            {
+                spi.write(10, buf, 0);
+                REQUIRE_FALSE(bus.isSelected());
+
+                REQUIRE(bus.out_buf.size() == 1);
+                REQUIRE(bus.out_buf[0] == 10);
+            }
+
+            SECTION("2 writes")
+            {
+                spi.write(10, buf, 4);
+                REQUIRE_FALSE(bus.isSelected());
+
+                REQUIRE(bus.out_buf.size() == 5);
+
+                REQUIRE(bus.out_buf[0] == 10);
+                REQUIRE(bufcmp(buf, bus.out_buf.data() + 1, 4));
+
+                spi.write(99, buf, 6);
+                REQUIRE_FALSE(bus.isSelected());
+
+                REQUIRE(bus.out_buf.size() == 12);
+
+                REQUIRE(bus.out_buf[5] == 99);
+                REQUIRE(bufcmp(buf, bus.out_buf.data() + 6, 6));
+            }
+        }
+
+        SECTION("raw write")
+        {
+            uint8_t buf[] = {1, 2, 3, 4, 5, 6};
+
+            spi.write(buf, 0);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.out_buf.size() == 0);
+
+            spi.write(buf, 4);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.out_buf.size() == 4);
+
+            REQUIRE(bufcmp(buf, bus.out_buf.data(), 4));
+
+            spi.write(buf, 6);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.out_buf.size() == 10);
+
+            REQUIRE(bufcmp(buf, bus.out_buf.data() + 4, 6));
+        }
+    }
+}
+
+TEST_CASE("SPITransaction - reads")
+{
+    MockSPIBus bus;
+
+    bus.in_buf = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+    SPIBusConfig config1;
+
+    config1.cpha = 1;
+    config1.br   = SPIBaudRate::DIV_32;
+
+    bus.expected_config = config1;
+
+    SECTION("Transaction")
+    {
+        SPISlave slave(bus, GpioPin(GPIOA_BASE, 1), config1);
+        SPITransaction spi(slave);
+
+        REQUIRE(bus.out_buf.size() == 0);
+
+        SECTION("1 byte reg read")
+        {
+
+            REQUIRE(spi.read(0x05) == 1);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.out_buf.size() == 1);
+            REQUIRE(bus.out_buf.back() == 0x85);
+
+            REQUIRE(spi.read(0x05, true) == 2);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.out_buf.size() == 2);
+            REQUIRE(bus.out_buf.back() == 0x85);
+
+            REQUIRE(spi.read(0x05, false) == 3);
+            REQUIRE_FALSE(bus.isSelected());
+
+            REQUIRE(bus.out_buf.size() == 3);
+            REQUIRE(bus.out_buf.back() == 0x05);
+        }
+
+        SECTION("multi byte reg read")
+        {
+            uint8_t read[4] = {0, 0, 0, 0};
+            uint8_t zero[4] = {0, 0, 0, 0};
+
+            spi.read(0x05, read, 0);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.out_buf.size() == 1);
+            REQUIRE(bus.out_buf.back() == 0x85);
+            REQUIRE(bufcmp(read, zero, 4));
+
+            spi.read(0x05, read, 3);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.out_buf.size() == 2);
+            REQUIRE(bus.out_buf.back() == 0x85);
+            REQUIRE(bufcmp(read, bus.in_buf.data(), 3));
+
+            spi.read(0x05, read, 3, true);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.out_buf.size() == 3);
+            REQUIRE(bus.out_buf.back() == 0x85);
+            REQUIRE(bufcmp(read, bus.in_buf.data() + 3, 3));
+
+            spi.read(0x05, read, 4, false);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.out_buf.size() == 4);
+            REQUIRE(bus.out_buf.back() == 0x05);
+            REQUIRE(bufcmp(read, bus.in_buf.data() + 6, 4));
+        }
+
+        SECTION("multi byte raw read")
+        {
+            uint8_t read[4] = {0, 0, 0, 0};
+            uint8_t zero[4] = {0, 0, 0, 0};
+
+            spi.read(read, 0);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.out_buf.size() == 0);
+            REQUIRE(bufcmp(read, zero, 4));
+
+            spi.read(read, 3);
+            REQUIRE_FALSE(bus.isSelected());
+            REQUIRE(bus.out_buf.size() == 0);
+            REQUIRE(bufcmp(read, bus.in_buf.data(), 3));
+        }
+    }
+}
+
+TEST_CASE("SPITransaction - transfer")
+{
+    MockSPIBus bus;
+
+    bus.in_buf = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+
+    SPIBusConfig config1;
+
+    config1.cpha = 1;
+    config1.br   = SPIBaudRate::DIV_32;
+
+    bus.expected_config = config1;
+
+    SECTION("Transaction")
+    {
+        SPISlave slave(bus, GpioPin(GPIOA_BASE, 1), config1);
+        SPITransaction spi(slave);
+
+        uint8_t buf[4]  = {4, 3, 2, 1};
+        uint8_t cmp[4] = {4, 3, 2, 1};
+
+        spi.transfer(buf, 0);
+        REQUIRE_FALSE(bus.isSelected());
+        REQUIRE(bus.out_buf.size() == 0);
+        REQUIRE(bufcmp(buf, cmp, 4));
+
+        spi.transfer(buf, 4);
+        REQUIRE_FALSE(bus.isSelected());
+        REQUIRE(bus.out_buf.size() == 4);
+        REQUIRE(bufcmp(buf, bus.in_buf.data(), 4));
+        REQUIRE(bufcmp(cmp, bus.out_buf.data(), 4));
+    }
+}
\ No newline at end of file
diff --git a/src/tests/catch/test-hardwaretimer.cpp b/src/tests/catch/test-hardwaretimer.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f954fdbe3ced5a579afc37a26aaecee851396ad6
--- /dev/null
+++ b/src/tests/catch/test-hardwaretimer.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ *
+ * 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.
+ */
+
+#ifdef STANDALONE_CATCH1_TEST
+#include "catch-tests-entry.cpp"
+#endif
+
+#include <drivers/HardwareTimer.h>
+#include <miosix.h>
+
+#include <utils/testutils/catch.hpp>
+
+using namespace miosix;
+
+class TimerTestFixture
+{
+public:
+    TimerTestFixture()
+        : timer32(TIM5, TimerUtils::getPrescalerInputFrequency(
+                            TimerUtils::InputClock::APB1)),
+          timer16(TIM10, TimerUtils::getPrescalerInputFrequency(
+                             TimerUtils::InputClock::APB2))
+    {
+        RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+        RCC->APB2ENR |= RCC_APB2ENR_TIM10EN;
+    }
+
+    ~TimerTestFixture()
+    {
+        timer16.stop();
+        timer32.stop();
+
+        RCC->APB1ENR &= ~RCC_APB1ENR_TIM5EN;
+        RCC->APB2ENR &= ~RCC_APB2ENR_TIM10EN;
+    }
+
+    HardwareTimer<uint32_t> timer32;
+    HardwareTimer<uint16_t> timer16;
+};
+
+TEST_CASE_METHOD(TimerTestFixture, "Test basic functionality")
+{
+    timer32.setPrescaler(63);
+    timer16.setPrescaler(127);
+
+    REQUIRE(timer32.getMaxDuration() == Approx(3272.356).margin(0.001));
+    REQUIRE(timer16.getMaxDuration() == Approx(0.049).margin(0.001));
+
+    REQUIRE(timer32.getResolution() == Approx(0.761).margin(0.001));
+    REQUIRE(timer16.getResolution() == Approx(0.761).margin(0.001));
+
+    REQUIRE(timer32.tick() == 0);
+    REQUIRE(timer16.tick() == 0);
+
+    REQUIRE(timer32.start() == 0);
+    REQUIRE(timer16.start() == 0);
+
+    Thread::sleep(10);
+
+    uint32_t tick32 = timer32.tick();
+    uint32_t tick16 = timer16.tick();
+
+    REQUIRE(timer32.toMilliSeconds(tick32) == Approx(10).margin(1));
+    REQUIRE(timer16.toMilliSeconds(tick16) == Approx(10).margin(1));
+
+    Thread::sleep(30);
+
+    REQUIRE(timer32.toMilliSeconds(timer32.tick() - tick32) ==
+            Approx(30).margin(1));
+    REQUIRE(timer16.toMilliSeconds(timer16.tick() - tick16) ==
+            Approx(30).margin(1));
+
+    tick32 = timer32.stop();
+    tick16 = timer16.stop();
+
+    Thread::sleep(20);
+
+    REQUIRE(timer32.tick() == tick32);
+    REQUIRE(timer16.tick() == tick16);
+}
+
+TEST_CASE_METHOD(TimerTestFixture, "Test long term precision")
+{
+    timer32.setPrescaler(63);
+    timer16.setPrescaler(65535);  // Max prescaler
+
+    REQUIRE(timer32.getMaxDuration() == Approx(3272.356).margin(0.001));
+    REQUIRE(timer16.getMaxDuration() == Approx(25.565).margin(0.001));
+
+    REQUIRE(timer32.getResolution() == Approx(0.761).margin(0.001));
+    REQUIRE(timer16.getResolution() == Approx(390.095).margin(0.001));
+
+    REQUIRE(timer32.start() == 0);
+    REQUIRE(timer16.start() == 0);
+
+    Thread::sleep(24000);
+
+    uint32_t tick32 = timer32.tick();
+    uint32_t tick16 = timer16.tick();
+
+    REQUIRE(timer32.toMilliSeconds(timer32.tick()) == Approx(24000).margin(1));
+    REQUIRE(timer16.toMilliSeconds(timer16.tick()) == Approx(24000).margin(1));
+
+    Thread::sleep(36000);
+
+    REQUIRE(timer32.toMilliSeconds(timer32.tick()) == Approx(60000).margin(1));
+}
\ No newline at end of file
diff --git a/src/tests/catch/test-matrix.cpp b/src/tests/catch/test-matrix.cpp
index 32ee05424d6897230642bc8afe077bdc30bb82a7..8a5ef3dbf9f2dfa995c4b32dd2c0b32dde170b3e 100644
--- a/src/tests/catch/test-matrix.cpp
+++ b/src/tests/catch/test-matrix.cpp
@@ -34,6 +34,8 @@ TEST_CASE("Multiply test")
     MatrixBase<float, 3, 1> B{1.0f, 2.0f, 3.7f};
     MatrixBase<float, 3, 1> C{};
 
+    C = A * B;
+
     REQUIRE( C(0,0) == Approx( 8.1f)  );
     REQUIRE( C(1,0) == Approx(-7.7f)  );
     REQUIRE( C(2,0) == Approx(-10.3f) );
@@ -46,6 +48,8 @@ TEST_CASE("Sum test")
 
     MatrixBase<float, 3, 3> C{};
 
+    C = A + B;
+
     REQUIRE( C(0,0) == Approx(2.0f ));
     REQUIRE( C(0,1) == Approx(0.0f ));
     REQUIRE( C(0,2) == Approx(6.0f ));
@@ -64,6 +68,8 @@ TEST_CASE("Subtract test")
 
     MatrixBase<float, 3, 3> C{};
 
+    C = A - B;
+    
     REQUIRE( C(0,0) == Approx(0.0f  ));
     REQUIRE( C(0,1) == Approx(-4.0f ));
     REQUIRE( C(0,2) == Approx(0.0f  ));
diff --git a/src/tests/drivers/test-dsgamma.cpp b/src/tests/drivers/test-dsgamma.cpp
index 5ff13c1baaaf45e57e67a4eb0b1f19bfada0b0dd..43bd00f67db139ddf5942af7f0469f30f259a391 100644
--- a/src/tests/drivers/test-dsgamma.cpp
+++ b/src/tests/drivers/test-dsgamma.cpp
@@ -16,7 +16,6 @@ using miosix::Gpio;
 
 /* DISCOVERY F429I*/
 typedef Gpio<GPIOA_BASE, 0> button;
-typedef HardwareTimer<uint32_t, 4> Clock;
 // RTT calculation
 // long long sendTime = 0;
 enum State
diff --git a/src/tests/drivers/test-l3gd20.cpp b/src/tests/drivers/test-l3gd20.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f5a63718ccb9427fc4f07f0d0f18afe5fc78ddd0
--- /dev/null
+++ b/src/tests/drivers/test-l3gd20.cpp
@@ -0,0 +1,240 @@
+/**
+ * Copyright (c) 2019 Skyward Experimental Rocketry
+ * Authors: Luca Erbetta
+ *
+ * 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 <array>
+
+#include "diagnostic/CpuMeter.h"
+#include "drivers/HardwareTimer.h"
+#include "drivers/spi/SPIDriver.h"
+#include "sensors/L3GD20.h"
+
+using namespace miosix;
+using std::array;
+
+typedef Gpio<GPIOF_BASE, 7> GpioSck;
+typedef Gpio<GPIOF_BASE, 8> GpioMiso;
+typedef Gpio<GPIOF_BASE, 9> GpioMosi;
+
+typedef Gpio<GPIOA_BASE, 2> GpioINT2;
+
+static constexpr bool FIFO_ENABLED           = true;
+static constexpr unsigned int FIFO_WATERMARK = 24;
+// Expected frequency from the datasheet is 760 Hz, but due to clock
+// misalignment / temperature errors and other factors, the observed clock (and
+// output data rate) is the following:
+static constexpr float SAMPLE_FREQUENCY = 782.3f;
+static constexpr int NUM_SAMPLES        = 10000;
+
+void enableInterrupt();
+
+struct GyroSample
+{
+    int fifo_num;
+    float timestamp;
+    Vec3 data;
+    int level;
+    float wtm_delta;
+    float cpu;
+};
+
+// 364 KB buffer to store up to 30 seconds of data @ 760 Hz
+GyroSample data[22800];
+int data_counter = 0;
+
+// High resolution hardware timer using TIM5
+HardwareTimer<uint32_t> hrclock{
+    TIM5, TimerUtils::getPrescalerInputFrequency(TimerUtils::InputClock::APB1)};
+
+// Last interrupt received timer tick
+volatile uint32_t last_watermark_tick;  // Stores the high-res tick of the last
+                                        // interrupt (L3GD20 watermark event)
+volatile uint32_t watermark_delta;  // Tick delta between the last 2 watermark
+                                    // events
+
+// L3GD20 SPI
+SPIBus bus(SPI5);
+GpioPin cs(GPIOC_BASE, 1);
+SPIBusConfig cfg;
+uint8_t buf[192];
+
+// Interrupt handlers
+void __attribute__((naked)) EXTI2_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20EXTI2_IRQHandlerImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) EXTI2_IRQHandlerImpl()
+{
+    uint32_t tick       = hrclock.tick();
+    watermark_delta     = tick - last_watermark_tick;
+    last_watermark_tick = tick;
+
+    EXTI->PR |= EXTI_PR_PR2;  // Reset pending register
+}
+
+int main()
+{
+    cfg.br = SPIBaudRate::DIV_64;
+
+    {
+        FastInterruptDisableLock dLock;
+
+        RCC->APB1ENR |= RCC_APB1ENR_TIM5EN;
+        RCC->APB2ENR |= RCC_APB2ENR_SPI5EN;
+
+        GpioSck::mode(Mode::ALTERNATE);
+        GpioMiso::mode(Mode::ALTERNATE);
+        GpioMosi::mode(Mode::ALTERNATE);
+
+        GpioSck::alternateFunction(5);
+        GpioMiso::alternateFunction(5);
+        GpioMosi::alternateFunction(5);
+
+        // Interrupt
+        GpioINT2::mode(Mode::INPUT_PULL_DOWN);
+
+        cs.mode(Mode::OUTPUT);
+    }
+    // High resolution clock configuration
+    // 1.8 hours run time, 1.5 us resolution
+    hrclock.setPrescaler(127);
+    hrclock.start();
+
+    enableInterrupt();
+    cs.high();
+
+    L3GD20 gyro(bus, cs, L3GD20::FullScaleRange::FS_250,
+                L3GD20::OutPutDataRate::ODR_760, 0x03, FIFO_ENABLED,
+                FIFO_WATERMARK);
+
+    while (!gyro.init())
+    {
+    }
+
+    Thread::sleep(500);
+
+    long long first_tick = miosix::getTick();
+
+    // Sample NUM_SAMPLES data
+    int fifo_num = 0;
+    while (data_counter < NUM_SAMPLES)
+    {
+        long last_tick = miosix::getTick();
+
+        if (FIFO_ENABLED)
+        {
+            // Precise timestamp of the last sample in the FIFO
+            float wtm_timestamp = hrclock.toSeconds(last_watermark_tick);
+            float wtm_delta     = hrclock.toMilliSeconds(watermark_delta);
+
+            // Read the fifo
+            gyro.onSimpleUpdate();
+
+            uint8_t level =
+                gyro.getLastFifoSize();  // Number of samples in the FIFO
+
+            const array<Vec3, 32>& fifo = gyro.getLastFifo();
+
+            for (int i = 0; i < level; i++)
+            {
+                // Assign a timestamp to each sample in the FIFO
+                // Samples before the watermark are older, after the watermark
+                // are younger. Time delta between samples is
+                // (1 / SAMPLE_FREQUENCY) seconds
+                float ts = wtm_timestamp +
+                           (i - (int)FIFO_WATERMARK) / SAMPLE_FREQUENCY;
+
+                data[data_counter++] = {fifo_num,  ts,
+                                        fifo[i],   level,
+                                        wtm_delta, averageCpuUtilization()};
+                if (data_counter >= NUM_SAMPLES)
+                {
+                    break;
+                }
+            }
+            ++fifo_num;
+
+            // Wait until fifo has about 25 samples
+            Thread::sleepUntil(last_tick + 34);
+        }
+        else
+        {
+            // Sample sensor @ 500 Hz
+            gyro.onSimpleUpdate();
+
+            data[data_counter++] = {0,
+                                    (last_tick - first_tick) / 1000.0f,
+                                    *(gyro.gyroDataPtr()),
+                                    0,
+                                    0,
+                                    averageCpuUtilization()};
+            Thread::sleepUntil(last_tick + 2);
+        }
+    }
+    // Dump buffer content as CSV on the serial (might take a while)
+    printf("FIFO_num,timestamp,watermark_delta,sample_delta,x,y,z,level,cpu");
+    for (int i = 1; i < data_counter; i++)
+    {
+        printf("%d,%.3f,%.3f,%.3f,%f,%f,%f,%d,%.2f\n", data[i].fifo_num,
+               data[i].timestamp * 1000, data[i].wtm_delta,
+               (data[i].timestamp - data[i - 1].timestamp) * 1000,
+               data[i].data.getX(), data[i].data.getY(), data[i].data.getZ(),
+               data[i].level, data[i].cpu);
+    }
+
+    printf("\n\n\nend.\n");
+    for (;;)
+    {
+        Thread::sleep(1000);
+    }
+}
+
+void enableInterrupt()
+{
+    {
+        FastInterruptDisableLock l;
+        RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;
+    }
+    // Refer to the datasheet for a detailed description on the procedure and
+    // interrupt registers
+
+    // Clear the mask on the wanted line
+    EXTI->IMR |= EXTI_IMR_MR2;
+
+    // Trigger the interrupt on a falling edge
+    // EXTI->FTSR |= EXTI_FTSR_TR2;
+
+    // Trigger the interrupt on a rising edge
+    EXTI->RTSR |= EXTI_RTSR_TR2;
+
+    EXTI->PR |= EXTI_PR_PR2;  // Reset pending register
+
+    // Enable interrupt on PA2 in SYSCFG
+    SYSCFG->EXTICR[0] &= 0xFFFFF0FF;
+
+    // // Enable the interrput in the interrupt controller
+    NVIC_EnableIRQ(EXTI2_IRQn);
+    NVIC_SetPriority(EXTI2_IRQn, 15);
+}
\ No newline at end of file
diff --git a/src/tests/drivers/test-ms5803.cpp b/src/tests/drivers/test-ms5803.cpp
index e35b80540ef18b2850160b9ba149b7592d233d2d..1a339433d914d7b5005246c6bf62f038f9b8a18e 100644
--- a/src/tests/drivers/test-ms5803.cpp
+++ b/src/tests/drivers/test-ms5803.cpp
@@ -21,27 +21,32 @@
  */
 
 #include <Common.h>
-#include <drivers/BusTemplate.h>
-#include <interfaces-impl/hwmapping.h>
-#include "sensors/MS580301BA07/MS580301BA07.h"
-
+#include <drivers/spi/SPIDriver.h>
 #include <drivers/spi/SensorSpi.h>
+#include <interfaces-impl/hwmapping.h>
 #include <sensors/SensorSampling.h>
 
+#include "sensors/MS580301BA07/MS580301BA07.h"
+
 using namespace miosix;
 using namespace miosix::interfaces;
-typedef Gpio<GPIOD_BASE, 7> cs_ms58;
 
-typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI1;
-typedef ProtocolSPI<busSPI1, cs_ms58> spiMS58;
-typedef MS580301BA07<spiMS58> ms58_t;
+SPIBus bus{SPI1};
+GpioPin chip_select{GPIOD_BASE, 7};
 
 int main()
 {
+    // Activate the SPI bus
+    {
+        FastInterruptDisableLock dLock;
+        RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;
+
+        // SCK, MISO, MOSI already initialized in the bsp
+    }
+
     SimpleSensorSampler sampler;
 
-    spiMS58::init();
-    ms58_t* ms58 = new ms58_t();
+    MS580301BA07* ms58 = new MS580301BA07(bus, chip_select);
 
     Thread::sleep(100);
 
@@ -70,8 +75,8 @@ int main()
         const float* last_pressure = ms58->pressureDataPtr();
         const float* last_temp     = ms58->tempDataPtr();
         MS5803Data md              = ms58->getData();
-        printf("%d,%f,%d,%f\n", (int)md.raw_press,
-               *last_pressure, (int)md.raw_temp, *last_temp);
+        printf("%d,%f,%d,%f\n", (int)md.raw_press, *last_pressure,
+               (int)md.raw_temp, *last_temp);
 
         Thread::sleep(100);
     }
diff --git a/src/tests/kalman/test-kalman-benchmark.cpp b/src/tests/kalman/test-kalman-benchmark.cpp
index bd9ba8ad8b949e9187c3700e48ceff2e0becb5dc..bbcd86bcda1eec3cff0b1f9fd7f94e46874f6e19 100644
--- a/src/tests/kalman/test-kalman-benchmark.cpp
+++ b/src/tests/kalman/test-kalman-benchmark.cpp
@@ -23,14 +23,15 @@
 // This prgram runs through a simulated flight and reports the apogee detection,
 // while measuring the time elapsed
 
-
 // RESULT: Update operation 0.0319 on average
 #include <Common.h>
 #include <drivers/HardwareTimer.h>
 #include <kalman/Kalman.h>
+
 #include <iostream>
-#include "util/util.h"
+
 #include "test-kalman-data.h"
+#include "util/util.h"
 
 using namespace miosix;
 
@@ -52,17 +53,18 @@ int main(int argc, char const* argv[])
     }
 
     // Timer for benchmarking purposes
-    HardwareTimer<uint32_t, 2>& timer = HardwareTimer<uint32_t, 2>::instance();
+    HardwareTimer<uint32_t> timer{TIM5, TimerUtils::getPrescalerInputFrequency(
+                                            TimerUtils::InputClock::APB1)};
 
     // Instanciate matrices
-    MatrixBase<float,3,3> P{0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1};
-    MatrixBase<float,1,1> V2{10};
-    MatrixBase<float,3,3> V1{0.01, 0, 0, 0, 0.01, 0, 0, 0, 0.01};
-    MatrixBase<float,1,3> C{1, 0, 0};
-    MatrixBase<float,3,3> A{1, 0, 0, 0, 1, 0, 0, 0, 1};
+    MatrixBase<float, 3, 3> P{0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1};
+    MatrixBase<float, 1, 1> V2{10};
+    MatrixBase<float, 3, 3> V1{0.01, 0, 0, 0, 0.01, 0, 0, 0, 0.01};
+    MatrixBase<float, 1, 3> C{1, 0, 0};
+    MatrixBase<float, 3, 3> A{1, 0, 0, 0, 1, 0, 0, 0, 1};
 
     // Instanciate filter object
-    Kalman<3,1> filter = Kalman<3,1>(A, C, V1, V2, P);
+    Kalman<3, 1> filter = Kalman<3, 1>(A, C, V1, V2, P);
 
     float last_time = 0.0;  // Variable to save the time of the last sample
     float time;             // Current time as read from csv file
@@ -78,7 +80,7 @@ int main(int argc, char const* argv[])
     {
         if (i == 0)
         {
-            filter.X(0,0) = INPUT[0];
+            filter.X(0, 0) = INPUT[0];
             continue;
         }
         time = TIME[i];
@@ -88,8 +90,8 @@ int main(int argc, char const* argv[])
         filter.A(0, 2) = 0.5 * T * T;
         filter.A(1, 2) = T;
 
-        MatrixBase<float,1,1> y{};
-        y(0,0) = INPUT[i];
+        MatrixBase<float, 1, 1> y{};
+        y(0, 0) = INPUT[i];
 
         tick1 = timer.tick();
         filter.update(y);
@@ -98,7 +100,7 @@ int main(int argc, char const* argv[])
         // printf("%f, %f, %f;\n", filter.X(0,0), filter.X(1,0), filter.X(2,0));
         // std::cout << MemoryProfiling::getCurrentFreeStack() << "\n";
         last_time = time;
-        if (filter.X(1,0) < 0)
+        if (filter.X(1, 0) < 0)
         {
             greenLed::high();
             redLed::low();
diff --git a/src/tests/misc/xbee-bitrate.cpp b/src/tests/misc/xbee-bitrate.cpp
index 229fa358f1f54b48fc16f47d6aeee8b5aaf8b239..c4b4f5f8ae287d4188130e707f22104e57d5832b 100644
--- a/src/tests/misc/xbee-bitrate.cpp
+++ b/src/tests/misc/xbee-bitrate.cpp
@@ -31,7 +31,7 @@
 #include "drivers/Xbee/Xbee.h"
 #include "math/Stats.h"
 
-#include <drivers/BusTemplate.h>
+#include <drivers/spi/SPIDriver.h>
 
 using std::cin;
 using std::cout;
@@ -43,20 +43,24 @@ static constexpr int PKT_NUM = 100;
 using namespace miosix;
 using namespace interfaces;
 
-// SPI1 binding al sensore
-
 // WARNING: If flashing on stm32f49 discovery board (with screen removed) use
 // SPI1 as the 2nd isnt working.
-typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2;  // Creo la SPI2
 
-// typedef BusSPI<1, spi2::mosi, spi2::miso, spi2::sck> busSPI2;  // Creo la
-// SPI2
+// Discovery
+SPIBus bus{SPI1};
+GpioPin cs = sensors::lsm6ds3h::cs::getPin();
+GpioPin attn = xbee::attn::getPin();
+GpioPin rst = xbee::reset::getPin();
+
+// Death stack
+// SPIBus bus{SPI2};
+// GpioPin cs = xbee::cs::getPin();
+// GpioPin attn = xbee::attn::getPin();
+// GpioPin rst = xbee::reset::getPin();
 
-// WARNING: Don't use xbee::cs on discovery board as it isn't working
-typedef Xbee::Xbee<busSPI2, sensors::lsm6ds3h::cs, xbee::attn, xbee::reset>
-    Xbee_t;
 
-Xbee_t xbee_transceiver;
+Xbee::Xbee* xbee_transceiver;
+
 void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); }
 
 void enableXbeeInterrupt()
@@ -102,7 +106,7 @@ bool sendPacket(uint8_t size)
     }
     ++snd_cntr;
 
-    if (!xbee_transceiver.send(snd_buf, size))
+    if (!xbee_transceiver->send(snd_buf, size))
     {
         return false;
     }
@@ -119,8 +123,9 @@ void resetXBee()
 int main()
 {
     enableXbeeInterrupt();
-    busSPI2::init();
-    xbee_transceiver.start();
+    xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst);
+
+    xbee_transceiver->start();
     resetXBee();
 
     printf("XBee bitrate measurement\n");
diff --git a/src/tests/misc/xbee-send-rcv.cpp b/src/tests/misc/xbee-send-rcv.cpp
index a5fced8963c8e89aa73735b3e7a93b8a918813af..6365eae4adccde76e97b29f9a472b6011a8210c4 100644
--- a/src/tests/misc/xbee-send-rcv.cpp
+++ b/src/tests/misc/xbee-send-rcv.cpp
@@ -21,8 +21,10 @@
  * THE SOFTWARE.
  */
 
+#include <drivers/spi/SPIDriver.h>
 #include <interfaces-impl/hwmapping.h>
 #include <miosix.h>
+
 #include <cstdio>
 #include <iostream>
 #include <string>
@@ -31,33 +33,36 @@
 #include "drivers/Xbee/Xbee.h"
 #include "math/Stats.h"
 
-#include <drivers/BusTemplate.h>
-
 using std::cin;
 using std::cout;
 using std::string;
 
-using HwTimer                      = HardwareTimer<uint32_t, 2>;
 static const unsigned int PKT_SIZE = 128;
 
 using namespace miosix;
 using namespace interfaces;
 
-// SPI1 binding al sensore
-
 // WARNING: If flashing on stm32f49 discovery board (with screen removed) use
 // SPI1 as the 2nd isnt working.
-// typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2;  // Creo la SPI2
 
-typedef BusSPI<2, spi2::mosi, spi2::miso, spi2::sck> busSPI2;  // Creo la
-// SPI2
+// Discovery
+SPIBus bus{SPI1};
+GpioPin cs = sensors::lsm6ds3h::cs::getPin();
+GpioPin attn = xbee::attn::getPin();
+GpioPin rst = xbee::reset::getPin();
 
-// WARNING: Don't use xbee::cs on discovery board as it isn't working
-typedef Xbee::Xbee<busSPI2, xbee::cs, xbee::attn, xbee::reset>
-    Xbee_t;
+// Death stack
+// SPIBus bus{SPI2};
+// GpioPin cs = xbee::cs::getPin();
+// GpioPin attn = xbee::attn::getPin();
+// GpioPin rst = xbee::reset::getPin();
 
-Xbee_t xbee_transceiver;
-void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); }
+Xbee::Xbee* xbee_transceiver;
+
+void __attribute__((used)) EXTI10_IRQHandlerImpl()
+{
+    Xbee::handleATTNInterrupt();
+}
 
 void enableXbeeInterrupt()
 {
@@ -103,7 +108,7 @@ void send()
             c = 48;
         }
 
-        if (!xbee_transceiver.send(buf, PKT_SIZE))
+        if (!xbee_transceiver->send(buf, PKT_SIZE))
         {
             printf("[%d] Send error %d\n", (int)getTick(), ++fail);
         }
@@ -124,7 +129,7 @@ void receive(void*)
     uint8_t buf[512];
     for (;;)
     {
-        ssize_t len = xbee_transceiver.receive(buf, 512);
+        ssize_t len = xbee_transceiver->receive(buf, 512);
         if (len <= 0)
         {
             printf("Receive failed.\n");
@@ -168,12 +173,9 @@ int main()
 
     // reset();
 
-    HwTimer& t = HwTimer::instance();
-    t.setPrescaler(1024);
-
-    busSPI2::init();
+    xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst);
 
-    xbee_transceiver.start();
+    xbee_transceiver->start();
 
     // Send & receive
     Thread::create(receive, 2048);
diff --git a/src/tests/misc/xbee-time-to-send.cpp b/src/tests/misc/xbee-time-to-send.cpp
index 4e2d8dc7a45cc7eedb5f3395c67f6cfa2a169a21..1418e671bffbcd03b7ae12717a974b650af10297 100644
--- a/src/tests/misc/xbee-time-to-send.cpp
+++ b/src/tests/misc/xbee-time-to-send.cpp
@@ -44,19 +44,24 @@ static constexpr int PKTS_PER_SECOND  = 4;
 using namespace miosix;
 using namespace interfaces;
 
-// SPI1 binding al sensore
-
 // WARNING: If flashing on stm32f49 discovery board (with screen removed) use
 // SPI1 as the 2nd isnt working.
-// typedef BusSPI<1, spi1::mosi, spi1::miso, spi1::sck> busSPI2;  // Creo la SPI2
 
-typedef BusSPI<2, spi2::mosi, spi2::miso, spi2::sck> busSPI2; 
+// Discovery
+SPIBus bus{SPI1};
+GpioPin cs = sensors::lsm6ds3h::cs::getPin();
+GpioPin attn = xbee::attn::getPin();
+GpioPin rst = xbee::reset::getPin();
+
+// Death stack
+// SPIBus bus{SPI2};
+// GpioPin cs = xbee::cs::getPin();
+// GpioPin attn = xbee::attn::getPin();
+// GpioPin rst = xbee::reset::getPin();
+
+Xbee::Xbee* xbee_transceiver;
 
-// WARNING: Don't use xbee::cs on discovery board as it isn't working
-typedef Xbee::Xbee<busSPI2, xbee::cs, xbee::attn, xbee::reset>
-    Xbee_t;
 
-Xbee_t xbee_transceiver;
 void __attribute__((used)) EXTI10_IRQHandlerImpl() { Xbee::handleATTNInterrupt(); }
 
 void enableXbeeInterrupt()
@@ -101,7 +106,7 @@ bool sendPacket(uint8_t size)
     }
     ++snd_cntr;
 
-    if (!xbee_transceiver.send(snd_buf, size))
+    if (!xbee_transceiver->send(snd_buf, size))
     {
         return false;
     }
@@ -112,8 +117,10 @@ bool sendPacket(uint8_t size)
 int main()
 {
     enableXbeeInterrupt();
-    busSPI2::init();
-    xbee_transceiver.start();
+
+    xbee_transceiver = new Xbee::Xbee(bus, cs, attn, rst);
+
+    xbee_transceiver->start();
 
     printf("XBee time-to-send-measurement\n");
     printf(