diff --git a/src/shared/utils/collections/CircularBuffer.h b/src/shared/utils/collections/CircularBuffer.h
index ea8e77bf51d155650f094e34edcde78deb484b15..8dfb8385efc639a3b12a097aad9f895dc32ed414 100644
--- a/src/shared/utils/collections/CircularBuffer.h
+++ b/src/shared/utils/collections/CircularBuffer.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Luca Erbetta
+ * Author: Luca Erbetta, Davide Mor
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -50,7 +50,7 @@ public:
      * @param elem Element to be added to the queue.
      * @return The element added.
      */
-    virtual T& put(const T& elem)
+    T& put(const T& elem)
     {
         buffer[writePtr] = elem;
         T& added         = buffer[writePtr];
@@ -78,7 +78,7 @@ public:
      * @param i Index of the element to get, starting from the oldest.
      * @return The element.
      */
-    virtual T& get(unsigned int i = 0)
+    T& get(unsigned int i = 0)
     {
         // Here we need to explicitly specify the name of this class because we
         // DO NOT want to call the version of count() overridden by a derived
@@ -102,7 +102,7 @@ public:
      * @throw range_error if buffer is empty.
      * @return The element.
      */
-    virtual T& last() { return get(count() - 1); }
+    T& last() { return get(count() - 1); }
 
     /**
      * @brief Pops the first element in the buffer.
@@ -111,7 +111,7 @@ public:
      * @throw range_error if buffer is empty.
      * @return The element that has been popped.
      */
-    virtual const T& pop()
+    const T& pop()
     {
         if (!empty)
         {
@@ -131,7 +131,7 @@ public:
      *
      * @return Number of elements in the buffer.
      */
-    virtual size_t count() const
+    size_t count() const
     {
         if (empty)
             return 0;
@@ -145,9 +145,9 @@ public:
         }
     }
 
-    virtual bool isEmpty() const { return empty; }
+    bool isEmpty() const { return empty; }
 
-    virtual bool isFull() const
+    bool isFull() const
     {
         // Same as in get(): explicitly specify the name of the class to avoid
         // deadlocks!
diff --git a/src/shared/utils/collections/IRQCircularBuffer.h b/src/shared/utils/collections/IRQCircularBuffer.h
index e27d15e2a37652df6e822d29fd8d3ff0cd3af104..276182957972a3a73b1afa070f82a0cfba1bea64 100644
--- a/src/shared/utils/collections/IRQCircularBuffer.h
+++ b/src/shared/utils/collections/IRQCircularBuffer.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Luca Erbetta
+ * Author: Luca Erbetta, Davide Mor
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -24,6 +24,8 @@
 
 #include <miosix.h>
 
+#include <type_traits>
+
 #include "CircularBuffer.h"
 
 using miosix::FastInterruptDisableLock;
@@ -38,20 +40,20 @@ namespace Boardcore
  * interrupt service routines.
  */
 template <typename T, unsigned int Size>
-class IRQCircularBuffer : public CircularBuffer<T, Size>
+class IRQCircularBuffer
 {
-
-    using Super = CircularBuffer<T, Size>;
+    static_assert(std::is_trivially_copy_constructible<T>::value,
+                  "T must be trivially copy constructible!");
 
 public:
     /**
      * @brief Puts a copy of the element in the buffer.
      */
-    T& put(const T& elem) override
+    T& put(const T& elem)
     {
         FastInterruptDisableLock d;
         IRQwakeWaitingThread();
-        return Super::put(elem);
+        return buffer.put(elem);
     }
 
     /**
@@ -65,10 +67,23 @@ public:
      * @param i Index of the element to get, starting from the oldest.
      * @return The element.
      */
-    T& get(unsigned int i = 0) override
+    T get(unsigned int i = 0)
+    {
+        FastInterruptDisableLock d;
+        return buffer.get(i);
+    }
+
+    /**
+     * @brief Returns the last element added in the buffer.
+     *
+     * @warning Remember to catch the exception!
+     * @throw range_error if buffer is empty.
+     * @return The element.
+     */
+    T last()
     {
         FastInterruptDisableLock d;
-        return Super::get(i);
+        return buffer.last();
     }
 
     /**
@@ -78,10 +93,10 @@ public:
      * @throw range_error if buffer is empty.
      * @return The element that has been popped.
      */
-    const T& pop() override
+    T pop()
     {
         FastInterruptDisableLock d;
-        return Super::pop();
+        return buffer.pop();
     }
 
     /**
@@ -89,22 +104,22 @@ public:
      *
      * @return Number of elements in the buffer.
      */
-    size_t count() const override
+    size_t count() const
     {
         FastInterruptDisableLock d;
-        return Super::count();
+        return buffer.count();
     }
 
-    bool isEmpty() const override
+    bool isEmpty() const
     {
         FastInterruptDisableLock d;
-        return Super::isEmpty();
+        return buffer.isEmpty();
     }
 
-    bool isFull() const override
+    bool isFull() const
     {
         FastInterruptDisableLock d;
-        return Super::isFull();
+        return buffer.isFull();
     }
 
     /**
@@ -112,10 +127,10 @@ public:
      *
      * @warning Only to be called inside an ISR or with interrupts disabled.
      */
-    T& IRQput(const T& elem)
+    void IRQput(const T& elem)
     {
         IRQwakeWaitingThread();
-        return Super::put(elem);
+        buffer.put(elem);
     }
 
     /**
@@ -127,14 +142,14 @@ public:
      * @param hppw Set to true if the woken thread is higher priority than the
      * current one, unchanged otherwise
      */
-    T& IRQput(const T& elem, bool& hppw)
+    void IRQput(const T& elem, bool& hppw)
     {
         if (waiting && (waiting->IRQgetPriority() >
                         Thread::IRQgetCurrentThread()->IRQgetPriority()))
             hppw = true;
 
         IRQwakeWaitingThread();
-        return Super::put(elem);
+        buffer.put(elem);
     }
 
     /**
@@ -150,7 +165,7 @@ public:
      * @param i Index of the element to get, starting from the oldest.
      * @return The element.
      */
-    T& IRQget(unsigned int i = 0) { return Super::get(i); }
+    T IRQget(unsigned int i = 0) { return buffer.get(i); }
 
     /**
      * @brief Pops the first element in the buffer.
@@ -161,7 +176,7 @@ public:
      * @throw range_error if buffer is empty.
      * @return The element that has been popped.
      */
-    const T& IRQpop() { return Super::pop(); }
+    T IRQpop() { return buffer.pop(); }
 
     /**
      * @brief Counts the elements in the buffer.
@@ -170,17 +185,17 @@ public:
      *
      * @return Number of elements in the buffer.
      */
-    size_t IRQcount() const { return Super::count(); }
+    size_t IRQcount() const { return buffer.count(); }
 
     /**
      * @warning Only to be called inside an ISR or with interrupts disabled.
      */
-    bool IRQisEmpty() const { return Super::isEmpty(); }
+    bool IRQisEmpty() const { return buffer.isEmpty(); }
 
     /**
      * @warning Only to be called inside an ISR or with interrupts disabled.
      */
-    bool IRQisFull() const { return Super::isFull(); }
+    bool IRQisFull() const { return buffer.isFull(); }
 
     /**
      * @brief Waits until the buffer contains at least one element.
@@ -188,7 +203,7 @@ public:
     void waitUntilNotEmpty()
     {
         FastInterruptDisableLock d;
-        while (Super::isEmpty())
+        while (buffer.isEmpty())
         {
             IRQwakeWaitingThread();
             waiting = Thread::IRQgetCurrentThread();
@@ -201,6 +216,14 @@ public:
         }
     }
 
+    /**
+     * @brief Returns the maximum number of elements that can be stored in the
+     * buffer.
+     *
+     * @return Buffer size.
+     */
+    size_t getSize() const { return Size; }
+
 private:
     void IRQwakeWaitingThread()
     {
@@ -212,6 +235,7 @@ private:
     }
 
     Thread* waiting = nullptr;
+    CircularBuffer<T, Size> buffer;
 };
 
 }  // namespace Boardcore
diff --git a/src/shared/utils/collections/SyncCircularBuffer.h b/src/shared/utils/collections/SyncCircularBuffer.h
index 27f757cd1fb03cc745f192f1940157b094149af1..eb2eeeb7d4c059d49b631a166eb90c69fbcd1cd3 100644
--- a/src/shared/utils/collections/SyncCircularBuffer.h
+++ b/src/shared/utils/collections/SyncCircularBuffer.h
@@ -1,5 +1,5 @@
 /* Copyright (c) 2015-2018 Skyward Experimental Rocketry
- * Author: Luca Erbetta
+ * Author: Luca Erbetta, Davide Mor
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -24,6 +24,8 @@
 
 #include <miosix.h>
 
+#include <type_traits>
+
 #include "CircularBuffer.h"
 
 using miosix::ConditionVariable;
@@ -37,21 +39,18 @@ namespace Boardcore
  * Implementation of a synchronized circular buffer
  */
 template <typename T, unsigned int Size>
-class SyncCircularBuffer : public CircularBuffer<T, Size>
+class SyncCircularBuffer
 {
-
-    using Super = CircularBuffer<T, Size>;
-
 public:
     /**
      * Puts a copy of the element in the buffer
      * @param elem element
      */
-    T& put(const T& elem) override
+    void put(const T& elem)
     {
         Lock<FastMutex> l(mutex);
         cv.signal();
-        return Super::put(elem);
+        buffer.put(elem);
     }
 
     /**
@@ -65,10 +64,23 @@ public:
      * @param i Index of the element to get, starting from the oldest.
      * @return The element.
      */
-    T& get(unsigned int i = 0) override
+    T get(unsigned int i = 0)
+    {
+        Lock<FastMutex> l(mutex);
+        return buffer.get(i);
+    }
+
+    /**
+     * @brief Returns the last element added in the buffer.
+     *
+     * @warning Remember to catch the exception!
+     * @throw range_error if buffer is empty.
+     * @return The element.
+     */
+    T last()
     {
         Lock<FastMutex> l(mutex);
-        return Super::get(i);
+        return buffer.last();
     }
 
     /**
@@ -78,10 +90,10 @@ public:
      * @throw range_error if buffer is empty.
      * @return The element that has been popped.
      */
-    const T& pop() override
+    T pop()
     {
         Lock<FastMutex> l(mutex);
-        return Super::pop();
+        return buffer.pop();
     }
 
     /**
@@ -89,22 +101,22 @@ public:
      *
      * @return Number of elements in the buffer.
      */
-    size_t count() const override
+    size_t count() const
     {
         Lock<FastMutex> l(mutex);
-        return Super::count();
+        return buffer.count();
     }
 
-    bool isEmpty() const override
+    bool isEmpty() const
     {
         Lock<FastMutex> l(mutex);
-        return Super::isEmpty();
+        return buffer.isEmpty();
     }
 
-    bool isFull() const override
+    bool isFull() const
     {
         Lock<FastMutex> l(mutex);
-        return Super::isFull();
+        return buffer.isFull();
     }
 
     /**
@@ -113,15 +125,25 @@ public:
     void waitUntilNotEmpty()
     {
         Lock<FastMutex> l(mutex);
-        while (Super::isEmpty())
+        while (buffer.isEmpty())
         {
             cv.wait(l);
         }
     }
 
+    /**
+     * @brief Returns the maximum number of elements that can be stored in the
+     * buffer.
+     *
+     * @return Buffer size.
+     */
+    size_t getSize() const { return Size; }
+
 private:
     mutable FastMutex mutex;
     mutable ConditionVariable cv;
+
+    CircularBuffer<T, Size> buffer;
 };
 
 }  // namespace Boardcore
diff --git a/src/shared/utils/collections/SyncPacketQueue.h b/src/shared/utils/collections/SyncPacketQueue.h
index 4a28254b2d2803c5f79dad9f3feb3a115abde7a3..b786f7f2916d27401446d351ff0a0c3011f8cfe0 100644
--- a/src/shared/utils/collections/SyncPacketQueue.h
+++ b/src/shared/utils/collections/SyncPacketQueue.h
@@ -271,7 +271,7 @@ public:
     /**
      * @return The oldest packet, without removing it from the queue.
      */
-    const Packet<pktLen>& get()
+    const Packet<pktLen> get()
     {
         Lock<FastMutex> l(mutex);
         return buffer.get();
@@ -280,7 +280,7 @@ public:
     /**
      * @return The oldest packet, removing it from the queue.
      */
-    const Packet<pktLen>& pop()
+    const Packet<pktLen> pop()
     {
         Lock<FastMutex> l(mutex);
         return buffer.pop();