diff --git a/CMakeLists.txt b/CMakeLists.txt
index bab43939a686d7586dd75b0a22557adbb5dde95f..05d709a1b3e759e450cd7f50b3a0fb7552d0901b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -285,7 +285,7 @@ add_executable(test-usart-f7 src/tests/drivers/usart/test-usart.cpp)
 sbs_target(test-usart-f7 stm32f767zi_nucleo)
 
 add_executable(test-dma-mem-to-mem src/tests/drivers/test-dma-mem-to-mem.cpp)
-sbs_target(test-dma-mem-to-mem stm32f407vg_stm32f4discovery)
+sbs_target(test-dma-mem-to-mem stm32f767zi_compute_unit)
 
 add_executable(test-i2c-driver-f4 src/tests/drivers/i2c/test-i2c-driver.cpp)
 sbs_target(test-i2c-driver-f4 stm32f429zi_stm32f4discovery)
diff --git a/src/shared/drivers/dma/DMA.cpp b/src/shared/drivers/dma/DMA.cpp
index d6b79b46cd1f28c4f07bf719b10f15a920861284..844c1fdba04a1577fbe5992b6a5f1929599a2461 100644
--- a/src/shared/drivers/dma/DMA.cpp
+++ b/src/shared/drivers/dma/DMA.cpp
@@ -579,6 +579,10 @@ void DMAStream::waitForTransferComplete()
         std::bind(&DMAStream::getTransferCompleteFlagStatus, this),
         std::bind(&DMAStream::clearTransferCompleteFlag, this),
         transferCompleteFlag, -1);
+
+#ifdef STM32F767xx
+    invalidateCache();
+#endif  // STM32F767xx
 }
 
 bool DMAStream::timedWaitForHalfTransfer(std::chrono::nanoseconds timeout_ns)
@@ -598,7 +602,52 @@ bool DMAStream::timedWaitForTransferComplete(
         std::bind(&DMAStream::getTransferCompleteFlagStatus, this),
         std::bind(&DMAStream::clearTransferCompleteFlag, this),
         transferCompleteFlag, timeout_ns.count());
+
+#ifdef STM32F767xx
+    invalidateCache();
+#endif  // STM32F767xx
+}
+
+#ifdef STM32F767xx
+void DMAStream::invalidateCache()
+{
+    /**
+     * STM32F7 boards use data cache. Unluckily the dma doesn't
+     * trigger the cache refresh.
+     * This means that when copying data to ram, the user won't
+     * see the result.
+     * This method check if cache invalidation is needed, and
+     * forces it if necessary.
+     *
+     * The memory being invalidated must be 32 bytes aligned.
+     */
+
+    // If the data was copied from memory to a peripheral there's
+    // no need to worry about cache
+    if (currentSetup.direction == DMATransaction::Direction::MEM_TO_PER)
+        return;
+
+    constexpr uint8_t CACHE_LINE_SIZE = 32;
+
+    // Aligned ptr: round down to the nearest address that is
+    // 32 bytes aligned
+    uintptr_t alignedPtr =
+        (uintptr_t)currentSetup.dstAddress & ~(CACHE_LINE_SIZE - 1);
+
+    // Evaluate how many bytes were added, due to the round down
+    uintptr_t diff = (uintptr_t)currentSetup.dstAddress - alignedPtr;
+
+    // Aligned size: compute the amount of bytes being invalidated
+    int32_t alignedSize = currentSetup.numberOfDataItems;
+    if (currentSetup.dstSize == DMATransaction::DataSize::BITS_16)
+        alignedSize *= 2;
+    else if (currentSetup.dstSize == DMATransaction::DataSize::BITS_32)
+        alignedSize *= 4;
+    alignedSize += diff;
+
+    SCB_InvalidateDCache_by_Addr((uint32_t*)alignedPtr, alignedSize);
 }
+#endif  // STM32F767xx
 
 void DMAStream::setHalfTransferCallback(std::function<void()> callback)
 {
diff --git a/src/shared/drivers/dma/DMA.h b/src/shared/drivers/dma/DMA.h
index cabb4de6f3994c6267b23aec010241a08a26562e..e23bec7120509a0ca12e9e07992f73279cff204f 100644
--- a/src/shared/drivers/dma/DMA.h
+++ b/src/shared/drivers/dma/DMA.h
@@ -161,6 +161,8 @@ public:
      * @brief Wait for the half transfer complete signal.
      * The caller waits for the corresponding interrupt, if enabled.
      * Otherwise it goes to polling mode on the flag.
+     * @warning In case cache is used, this method DOES NOT invalidate
+     * the cache lines. It is up to the user.
      */
     void waitForHalfTransfer();
 
@@ -168,6 +170,8 @@ public:
      * @brief Wait for the transfer complete signal.
      * The caller waits for the corresponding interrupt, if enabled.
      * Otherwise it goes to polling mode on the flag.
+     * In case cache is used, this method invalidates the
+     * cache lines, so that the user can see the memory as is in ram.
      */
     void waitForTransferComplete();
 
@@ -178,6 +182,8 @@ public:
      * @param timeout_ns The maximum time that will be waited.
      * @return True if the event is reached, false if the
      * timeout expired.
+     * @warning In case cache is used, this method DOES NOT invalidate
+     * the cache lines. It is up to the user.
      */
     bool timedWaitForHalfTransfer(std::chrono::nanoseconds timeout_ns);
 
@@ -185,6 +191,8 @@ public:
      * @brief Wait for the transfer complete signal.
      * The caller waits for the corresponding interrupt, if enabled.
      * Otherwise it goes to polling mode on the flag.
+     * In case cache is used, this method invalidates the
+     * cache lines, so that the user can see the memory as is in ram.
      * @param timeout_ns The maximum time that will be waited.
      * @return True if the event is reached, false if the
      * timeout expired.
@@ -433,6 +441,16 @@ private:
         return result;
     }
 
+#ifdef STM32F767xx
+    /**
+     * @brief In case cache is used and data is written to ram,
+     * we have to invalidate cache lines in order to see the
+     * updated data. This function verifies if this operation
+     * is needed and performs it.
+     */
+    void invalidateCache();
+#endif  // STM32F767xx
+
 public:
     DMAStream(const DMAStream&)            = delete;
     DMAStream& operator=(const DMAStream&) = delete;