From 6d42a48fa36d9ec9e8075a0a39ea6b5ee65886f6 Mon Sep 17 00:00:00 2001
From: Fabrizio Monti <fabrizio.monti@skywarder.eu>
Date: Thu, 5 Jun 2025 19:48:10 +0000
Subject: [PATCH] [DMA] DMA driver

---
 CMakeLists.txt                                |   3 +
 cmake/boardcore.cmake                         |   2 +
 src/shared/drivers/dma/DMA.cpp                | 763 ++++++++++++++++++
 src/shared/drivers/dma/DMA.h                  | 573 +++++++++++++
 src/shared/drivers/dma/DMADefs.cpp            |  69 ++
 src/shared/drivers/dma/DMADefs.h              | 257 ++++++
 src/shared/drivers/dma/DMAStream.h            | 510 ------------
 .../board_mappings/stm32f407xx_mappings.cpp   | 232 ++++++
 .../board_mappings/stm32f429xx_mappings.cpp   | 262 ++++++
 .../board_mappings/stm32f767xx_mappings.cpp   | 307 +++++++
 src/tests/drivers/test-dma-mem-to-mem.cpp     |  87 ++
 11 files changed, 2555 insertions(+), 510 deletions(-)
 create mode 100644 src/shared/drivers/dma/DMA.cpp
 create mode 100644 src/shared/drivers/dma/DMA.h
 create mode 100644 src/shared/drivers/dma/DMADefs.cpp
 create mode 100644 src/shared/drivers/dma/DMADefs.h
 delete mode 100644 src/shared/drivers/dma/DMAStream.h
 create mode 100644 src/shared/drivers/dma/board_mappings/stm32f407xx_mappings.cpp
 create mode 100644 src/shared/drivers/dma/board_mappings/stm32f429xx_mappings.cpp
 create mode 100644 src/shared/drivers/dma/board_mappings/stm32f767xx_mappings.cpp
 create mode 100644 src/tests/drivers/test-dma-mem-to-mem.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f1ed2c694..05d709a1b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -284,6 +284,9 @@ sbs_target(test-usart-f4 stm32f429zi_stm32f4discovery)
 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 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/cmake/boardcore.cmake b/cmake/boardcore.cmake
index 503803036..1e306418a 100644
--- a/cmake/boardcore.cmake
+++ b/cmake/boardcore.cmake
@@ -58,6 +58,8 @@ set(BOARDCORE_SRC
     ${BOARDCORE_PATH}/src/shared/drivers/canbus/CanDriver/CanDriver.cpp
     ${BOARDCORE_PATH}/src/shared/drivers/canbus/CanDriver/CanInterrupt.cpp
     ${BOARDCORE_PATH}/src/shared/drivers/canbus/CanProtocol/CanProtocol.cpp
+    ${BOARDCORE_PATH}/src/shared/drivers/dma/DMA.cpp
+    ${BOARDCORE_PATH}/src/shared/drivers/dma/DMADefs.cpp
     ${BOARDCORE_PATH}/src/shared/drivers/interrupt/external_interrupts.cpp
     ${BOARDCORE_PATH}/src/shared/drivers/timer/PWM.cpp
     ${BOARDCORE_PATH}/src/shared/drivers/timer/CountedPWM.cpp
diff --git a/src/shared/drivers/dma/DMA.cpp b/src/shared/drivers/dma/DMA.cpp
new file mode 100644
index 000000000..fd87b8cc8
--- /dev/null
+++ b/src/shared/drivers/dma/DMA.cpp
@@ -0,0 +1,763 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio, Fabrizio Monti
+ *
+ * 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 "DMA.h"
+
+#include <kernel/logging.h>
+#include <utils/ClockUtils.h>
+#include <utils/Debug.h>
+
+#include <map>
+
+using namespace miosix;
+
+/**
+ * Here are defined the IRQHandlers for the various streams.
+ *
+ * The problem is that some of these stream are used
+ * by miosix. The corresponding IRQHandlers are already defined
+ * in there, causing conflicts.
+ * Moreover, the used streams might differ from different boards.
+ * That's why some streams are available only for a particular board.
+ */
+
+void __attribute__((naked)) DMA1_Stream0_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream0_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream0_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str0);
+}
+
+#ifndef STM32F407xx
+// This stream is used by miosix for STM32F407xx boards
+void __attribute__((naked)) DMA1_Stream1_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream1_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream1_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str1);
+}
+#endif  // STM32F407xx
+
+void __attribute__((naked)) DMA1_Stream2_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream2_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream2_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str2);
+}
+
+#ifndef STM32F407xx
+// This stream is used by miosix for STM32F407xx boards
+void __attribute__((naked)) DMA1_Stream3_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream3_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream3_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str3);
+}
+#endif  // STM32F407xx
+
+void __attribute__((naked)) DMA1_Stream4_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream4_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream4_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str4);
+}
+
+void __attribute__((naked)) DMA1_Stream5_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream5_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream5_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str5);
+}
+
+void __attribute__((naked)) DMA1_Stream6_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream6_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream6_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str6);
+}
+
+void __attribute__((naked)) DMA1_Stream7_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA1_Stream7_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA1_Stream7_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA1_Str7);
+}
+
+void __attribute__((naked)) DMA2_Stream0_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA2_Stream0_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA2_Stream0_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA2_Str0);
+}
+
+void __attribute__((naked)) DMA2_Stream1_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA2_Stream1_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA2_Stream1_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA2_Str1);
+}
+
+void __attribute__((naked)) DMA2_Stream2_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA2_Stream2_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA2_Stream2_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA2_Str2);
+}
+
+// This stream is used by miosix for all currently supported
+// boards, so it is simply commented out
+// void __attribute__((naked)) DMA2_Stream3_IRQHandler()
+// {
+//     saveContext();
+//     asm volatile("bl _Z20DMA2_Stream3_IRQImplv");
+//     restoreContext();
+// }
+
+// void __attribute__((used)) DMA2_Stream3_IRQImpl()
+// {
+//     Boardcore::DMADriver::instance().IRQhandleInterrupt(
+//         Boardcore::DMADefs::DMAStreamId::DMA2_Str3);
+// }
+
+void __attribute__((naked)) DMA2_Stream4_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA2_Stream4_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA2_Stream4_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA2_Str4);
+}
+
+#if !defined(STM32F767xx) && !defined(STM32F429xx)
+// This stream is used by miosix for STM32F767xx
+// and STM32F429xx boards
+void __attribute__((naked)) DMA2_Stream5_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA2_Stream5_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA2_Stream5_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA2_Str5);
+}
+#endif  // STM32F767xx & STM32F429xx
+
+void __attribute__((naked)) DMA2_Stream6_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA2_Stream6_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA2_Stream6_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA2_Str6);
+}
+
+#if !defined(STM32F767xx) && !defined(STM32F429xx)
+// This stream is used by miosix for STM32F767xx
+// and STM32F429xx boards
+void __attribute__((naked)) DMA2_Stream7_IRQHandler()
+{
+    saveContext();
+    asm volatile("bl _Z20DMA2_Stream7_IRQImplv");
+    restoreContext();
+}
+
+void __attribute__((used)) DMA2_Stream7_IRQImpl()
+{
+    Boardcore::DMADriver::instance().IRQhandleInterrupt(
+        Boardcore::DMADefs::DMAStreamId::DMA2_Str7);
+}
+#endif  // STM32F767xx & STM32F429xx
+
+namespace Boardcore
+{
+
+void DMADriver::IRQhandleInterrupt(DMADefs::DMAStreamId id)
+{
+    DMAStream& stream = streams.at(id);
+
+    stream.readFlags();
+    stream.clearAllFlags();
+
+    // Run the callbacks if necessary
+    if (stream.halfTransferCallback && stream.halfTransferFlag)
+        stream.halfTransferCallback();
+
+    if (stream.transferCompleteCallback && stream.transferCompleteFlag)
+        stream.transferCompleteCallback();
+
+    if (stream.errorCallback &&
+        (stream.transferErrorFlag || stream.fifoErrorFlag ||
+         stream.directModeErrorFlag))
+    {
+        stream.errorCallback();
+    }
+
+    // Wakeup the thread if the user is waiting
+    if (stream.waitingThread)
+        IRQwakeupThread(stream);
+}
+
+void DMADriver::IRQwakeupThread(DMAStream& stream)
+{
+    // Wakeup the waiting thread
+    stream.waitingThread->wakeup();
+
+    // If the waiting thread has a higher priority than the current
+    // thread then reschedule
+    if (stream.waitingThread->IRQgetPriority() >
+        miosix::Thread::IRQgetCurrentThread()->IRQgetPriority())
+    {
+        miosix::Scheduler::IRQfindNextThread();
+    }
+
+    // Clear the thread pointer, this way the thread will be sure it is
+    // not a spurious wakeup
+    stream.waitingThread = nullptr;
+}
+
+DMADriver& DMADriver::instance()
+{
+    static DMADriver instance;
+    return instance;
+}
+
+bool DMADriver::tryStream(DMADefs::DMAStreamId id)
+{
+    Lock<FastMutex> l(mutex);
+
+    // Return true, meaning that the channel is free, only if it is not yet
+    // allocated
+    return streams.count(id) == 0;
+}
+
+DMAStreamGuard DMADriver::acquireStream(DMADefs::DMAStreamId id,
+                                        DMADefs::Channel channel,
+                                        std::chrono::nanoseconds timeout)
+{
+    Lock<FastMutex> l(mutex);
+
+    // Wait until the stream is free or the timeout expires
+    while (streams.count(id) != 0)
+    {
+        if (timeout == std::chrono::nanoseconds::zero())
+        {
+            cv.wait(l);
+        }
+        else
+        {
+            auto res = cv.timedWait(l, timeout.count());
+
+            if (res == TimedWaitResult::Timeout)
+            {
+                // The timeout expired
+                return DMAStreamGuard(nullptr);
+            }
+        }
+    }
+
+    streams.insert(
+        std::pair<DMADefs::DMAStreamId, DMAStream>(id, DMAStream(id, channel)));
+    return DMAStreamGuard(&(streams.at(id)));
+}
+
+DMAStreamGuard DMADriver::acquireStreamForPeripheral(
+    DMADefs::Peripherals peripheral, std::chrono::nanoseconds timeout)
+{
+    const auto availableStreams =
+        DMADefs::mapPeripherals.equal_range(peripheral);
+
+    Lock<FastMutex> l(mutex);
+    while (true)
+    {
+        // Iterate through the streams for that peripheral,
+        // return the first available
+        for (auto it = availableStreams.first; it != availableStreams.second;
+             ++it)
+        {
+            DMADefs::DMAStreamId id  = it->second.first;
+            DMADefs::Channel channel = it->second.second;
+
+            if (streams.count(id) == 0)
+            {
+                // Stream is free
+                streams.insert(std::pair<DMADefs::DMAStreamId, DMAStream>(
+                    id, DMAStream(id, channel)));
+                return DMAStreamGuard(&(streams.at(id)));
+            }
+        }
+
+        if (timeout == std::chrono::nanoseconds::zero())
+        {
+            cv.wait(l);
+        }
+        else
+        {
+            auto res = cv.timedWait(l, timeout.count());
+
+            if (res == TimedWaitResult::Timeout)
+            {
+                // The timeout expired
+                return DMAStreamGuard(nullptr);
+            }
+        }
+    }
+}
+
+void DMADriver::releaseStream(DMADefs::DMAStreamId id)
+{
+    Lock<FastMutex> l(mutex);
+
+    if (streams.count(id) != 0)
+    {
+        streams.erase(id);
+        cv.broadcast();
+    }
+}
+
+DMADriver::DMADriver()
+{
+    // For now the clocks are always enabled
+    ClockUtils::enablePeripheralClock(DMA1);
+    ClockUtils::enablePeripheralClock(DMA2);
+
+    // Reset interrupts flags by setting the clear bits to 1
+    constexpr int resetValue = 0x0f7d0f7d;
+    DMA1->HIFCR              = resetValue;
+    DMA1->LIFCR              = resetValue;
+    DMA2->HIFCR              = resetValue;
+    DMA2->LIFCR              = resetValue;
+}
+
+void DMAStream::setup(DMATransaction& transaction)
+{
+    currentSetup = transaction;
+
+    // Reset the configuration
+    registers->CR = 0;
+
+    // Wait for the stream to actually be disabled
+    while (registers->CR & DMA_SxCR_EN)
+        ;
+
+    setChannel(currentChannel);
+    registers->CR |= static_cast<uint32_t>(transaction.direction);
+    registers->CR |= static_cast<uint32_t>(transaction.priority);
+    if (transaction.circularMode)
+        registers->CR |= DMA_SxCR_CIRC;
+
+    setNumberOfDataItems(transaction.numberOfDataItems);
+
+    if (transaction.direction == DMATransaction::Direction::MEM_TO_PER)
+    {
+        // In memory to peripheral mode, the source address is the memory
+        // address
+
+        registers->CR |= static_cast<uint32_t>(transaction.srcSize)
+                         << DMA_SxCR_MSIZE_Pos;
+        registers->CR |= static_cast<uint32_t>(transaction.dstSize)
+                         << DMA_SxCR_PSIZE_Pos;
+
+        if (transaction.srcIncrement)
+            registers->CR |= DMA_SxCR_MINC;
+        if (transaction.dstIncrement)
+            registers->CR |= DMA_SxCR_PINC;
+
+        registers->M0AR = reinterpret_cast<uint32_t>(transaction.srcAddress);
+        registers->PAR  = reinterpret_cast<uint32_t>(transaction.dstAddress);
+    }
+    else
+    {
+        // In peripheral to memory or memory to memory mode, the source address
+        // goes into the peripheral address register
+
+        registers->CR |= static_cast<uint32_t>(transaction.srcSize)
+                         << DMA_SxCR_PSIZE_Pos;
+        registers->CR |= static_cast<uint32_t>(transaction.dstSize)
+                         << DMA_SxCR_MSIZE_Pos;
+
+        if (transaction.srcIncrement)
+            registers->CR |= DMA_SxCR_PINC;
+        if (transaction.dstIncrement)
+            registers->CR |= DMA_SxCR_MINC;
+
+        registers->PAR  = reinterpret_cast<uint32_t>(transaction.srcAddress);
+        registers->M0AR = reinterpret_cast<uint32_t>(transaction.dstAddress);
+    }
+
+    if (transaction.doubleBufferMode)
+    {
+        registers->CR |= DMA_SxCR_DBM;
+        registers->M1AR =
+            reinterpret_cast<uint32_t>(transaction.secondMemoryAddress);
+    }
+
+    bool enableInterrupt = false;
+    if (transaction.enableHalfTransferInterrupt)
+    {
+        clearHalfTransferFlag();
+        registers->CR |= DMA_SxCR_HTIE;
+        enableInterrupt = true;
+    }
+    if (transaction.enableTransferCompleteInterrupt)
+    {
+        clearTransferCompleteFlag();
+        registers->CR |= DMA_SxCR_TCIE;
+        enableInterrupt = true;
+    }
+    if (transaction.enableTransferErrorInterrupt)
+    {
+        clearTransferErrorFlag();
+        registers->CR |= DMA_SxCR_TEIE;
+        enableInterrupt = true;
+    }
+    if (transaction.enableFifoErrorInterrupt)
+    {
+        clearFifoErrorFlag();
+        registers->FCR |= DMA_SxFCR_FEIE;
+        enableInterrupt = true;
+    }
+    if (transaction.enableDirectModeErrorInterrupt)
+    {
+        clearDirectModeErrorFlag();
+        registers->CR |= DMA_SxCR_DMEIE;
+        enableInterrupt = true;
+    }
+
+    // Select the interrupt number
+    IRQn_Type irqNumber = DMADefs::irqNumberMapping[static_cast<uint8_t>(id)];
+    if (enableInterrupt)
+    {
+        NVIC_SetPriority(irqNumber, 8);
+        NVIC_ClearPendingIRQ(irqNumber);
+        NVIC_EnableIRQ(irqNumber);
+    }
+    else
+    {
+        NVIC_DisableIRQ(irqNumber);
+    }
+}
+
+void DMAStream::enable()
+{
+    // Reset all saved flags
+    halfTransferFlag     = false;
+    transferCompleteFlag = false;
+    transferErrorFlag    = false;
+    fifoErrorFlag        = false;
+    directModeErrorFlag  = false;
+
+    // Before setting EN bit to '1' to start a new transfer, the event
+    // flags corresponding to the stream in DMA_LISR or DMA_HISR
+    // register must be cleared.
+    clearAllFlags();
+
+    // Enable the peripheral
+    registers->CR |= DMA_SxCR_EN;
+}
+
+void DMAStream::disable() { registers->CR &= ~DMA_SxCR_EN; }
+
+void DMAStream::waitForHalfTransfer()
+{
+    waitForInterruptEventImpl(
+        currentSetup.enableHalfTransferInterrupt,
+        std::bind(&DMAStream::getHalfTransferFlagStatus, this),
+        std::bind(&DMAStream::clearHalfTransferFlag, this), halfTransferFlag,
+        -1);
+}
+
+void DMAStream::waitForTransferComplete()
+{
+    waitForInterruptEventImpl(
+        currentSetup.enableTransferCompleteInterrupt,
+        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)
+{
+    return waitForInterruptEventImpl(
+        currentSetup.enableHalfTransferInterrupt,
+        std::bind(&DMAStream::getHalfTransferFlagStatus, this),
+        std::bind(&DMAStream::clearHalfTransferFlag, this), halfTransferFlag,
+        timeout_ns.count());
+}
+
+bool DMAStream::timedWaitForTransferComplete(
+    std::chrono::nanoseconds timeout_ns)
+{
+    bool ret = waitForInterruptEventImpl(
+        currentSetup.enableTransferCompleteInterrupt,
+        std::bind(&DMAStream::getTransferCompleteFlagStatus, this),
+        std::bind(&DMAStream::clearTransferCompleteFlag, this),
+        transferCompleteFlag, timeout_ns.count());
+
+#ifdef STM32F767xx
+    invalidateCache();
+#endif  // STM32F767xx
+
+    return ret;
+}
+
+#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.
+     *
+     * As of today, the cache is set as write-through. This
+     * means that values written in cache are immediately
+     * written in ram, and we don't need to worry about memory
+     * to peripheral operations,
+     */
+
+    // 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)
+{
+    halfTransferCallback = callback;
+}
+
+void DMAStream::resetHalfTransferCallback() { halfTransferCallback = nullptr; }
+
+void DMAStream::setTransferCompleteCallback(std::function<void()> callback)
+{
+    transferCompleteCallback = callback;
+}
+
+void DMAStream::resetTransferCompleteCallback()
+{
+    transferCompleteCallback = nullptr;
+}
+
+void DMAStream::setErrorCallback(std::function<void()> callback)
+{
+    errorCallback = callback;
+}
+
+void DMAStream::resetErrorCallback() { errorCallback = nullptr; }
+
+void DMAStream::readFlags()
+{
+    uint8_t flags = *ISR >> IFindex;
+
+    halfTransferFlag     = flags & DMA_LISR_HTIF0;
+    transferCompleteFlag = flags & DMA_LISR_TCIF0;
+    transferErrorFlag    = flags & DMA_LISR_TEIF0;
+    fifoErrorFlag        = flags & DMA_LISR_DMEIF0;
+    directModeErrorFlag  = flags & DMA_LISR_DMEIF0;
+}
+
+bool DMAStream::setNumberOfDataItems(const uint16_t nBytes)
+{
+    // Verify that the stream is disabled while doing it
+    if ((registers->CR & DMA_SxCR_EN) != 0)
+    {
+        // Cannot proceed
+        return false;
+    }
+
+    currentSetup.numberOfDataItems = nBytes;
+    registers->NDTR                = nBytes;
+    return true;
+}
+
+void DMAStream::setChannel(const DMADefs::Channel channel)
+{
+    registers->CR |= static_cast<uint32_t>(channel);
+}
+
+int DMAStream::getCurrentBufferNumber()
+{
+    return (registers->CR & DMA_SxCR_CT) != 0 ? 2 : 1;
+}
+
+DMAStream::DMAStream(DMADefs::DMAStreamId id, DMADefs::Channel channel)
+    : id(id), currentChannel(channel)
+{
+    // Get the channel registers base address and the interrupt flags clear
+    // register address
+    if (id < DMADefs::DMAStreamId::DMA2_Str0)
+    {
+        registers = reinterpret_cast<DMA_Stream_TypeDef*>(
+            DMA1_BASE + 0x10 + 0x18 * static_cast<uint8_t>(id));
+
+        if (id < DMADefs::DMAStreamId::DMA1_Str4)
+        {
+            // Streams from 0 to 3 use low registers (LIFCR and LISR)
+            IFCR = &DMA1->LIFCR;
+            ISR  = &DMA1->LISR;
+        }
+        else
+        {
+            // Streams from 4 to 7 use high registers (HIFCR and HISR)
+            IFCR = &DMA1->HIFCR;
+            ISR  = &DMA1->HISR;
+        }
+    }
+    else
+    {
+        registers = reinterpret_cast<DMA_Stream_TypeDef*>(
+            DMA2_BASE + 0x10 + 0x18 * (static_cast<uint8_t>(id) - 8));
+
+        if (id < DMADefs::DMAStreamId::DMA2_Str4)
+        {
+            // Streams from 0 to 3 use low registers (LIFCR and LISR)
+            IFCR = &DMA2->LIFCR;
+            ISR  = &DMA2->LISR;
+        }
+        else
+        {
+            // Streams from 4 to 7 use high registers (HIFCR and HISR)
+            IFCR = &DMA2->HIFCR;
+            ISR  = &DMA2->HISR;
+        }
+    }
+
+    // Compute the index for the interrupt flags clear register
+    // Refer to reference manual for the register bits structure
+    int offset = static_cast<uint8_t>(id) % 4;
+    IFindex    = (offset % 2) * 6 + (offset / 2) * 16;
+}
+
+DMAStream* DMAStreamGuard::operator->()
+{
+    D(assert((pStream != nullptr) && "DMAStreamGuard: pointer is null"));
+
+    return pStream;
+}
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/dma/DMA.h b/src/shared/drivers/dma/DMA.h
new file mode 100644
index 000000000..fef7afc4d
--- /dev/null
+++ b/src/shared/drivers/dma/DMA.h
@@ -0,0 +1,573 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Alberto Nidasio, Fabrizio Monti
+ *
+ * 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 <kernel/scheduler/scheduler.h>
+#include <kernel/sync.h>
+
+#include <chrono>
+#include <functional>
+#include <map>
+
+#include "DMADefs.h"
+
+namespace Boardcore
+{
+
+/**
+ * @brief This is the configuration struct for
+ * a DMA transaction.
+ */
+struct DMATransaction
+{
+    enum class Direction : uint16_t
+    {
+        MEM_TO_MEM = DMA_SxCR_DIR_1,
+        MEM_TO_PER = DMA_SxCR_DIR_0,
+        PER_TO_MEM = 0,
+    };
+
+    /**
+     * Priority of the DMA transaction. When multiple
+     * streams from the same controller (DMA1 or DMA2)
+     * are requested, they are served following the
+     * priority order.
+     * If two requests have the same software priority level,
+     * the stream with the lower number takes priority over
+     * the stream with the higher number. For example, Stream
+     * 2 takes priority over Stream 4.
+     */
+    enum class Priority : uint32_t
+    {
+        VERY_HIGH = DMA_SxCR_PL,
+        HIGH      = DMA_SxCR_PL_1,
+        MEDIUM    = DMA_SxCR_PL_0,
+        LOW       = 0,
+    };
+
+    enum class DataSize : uint8_t
+    {
+        BITS_8 = 0,
+        BITS_16,
+        BITS_32,
+    };
+
+    Direction direction                = Direction::MEM_TO_MEM;
+    Priority priority                  = Priority::LOW;
+    DataSize srcSize                   = DataSize::BITS_32;
+    DataSize dstSize                   = DataSize::BITS_32;
+    volatile void* srcAddress          = nullptr;
+    volatile void* dstAddress          = nullptr;
+    volatile void* secondMemoryAddress = nullptr;
+    uint16_t numberOfDataItems         = 0;
+    bool srcIncrement                  = false;
+    bool dstIncrement                  = false;
+
+    /**
+     * @brief Enables circular buffer mode.
+     * @warning Not available with memory to memory transfers.
+     */
+    bool circularMode = false;
+
+    /**
+     * @brief Enables double buffer mode.
+     * @warning Automatically enables circular mode. Not
+     * available with memory to memory transfers.
+     */
+    bool doubleBufferMode                = false;
+    bool enableTransferCompleteInterrupt = false;
+
+    /**
+     * @warning After a full DMA transaction, cache invalidation is required
+     * to maintain data coherence. However, the half transfer
+     * mechanism does not allow the driver to handle cache
+     * invalidation internally, leaving the job to the user.
+     */
+    bool enableHalfTransferInterrupt = false;
+
+    /**
+     * The transfer error interrupt flag is set when:
+     * - A bus error occurs during a DMA read or a write access.
+     * - A write access is requested by software on a memory address register
+     * in Double buffer mode whereas the stream is enabled and the current
+     * target memory is the one impacted by the write into the memory address
+     * register.
+     */
+    bool enableTransferErrorInterrupt = false;
+
+    /**
+     * Fifo overrun/underrun condition.
+     *
+     * In direct mode, the FIFO error flag can also be set under
+     * the following conditions:
+     * - In the peripheral-to-memory mode, the FIFO can be saturated
+     * (overrun) if the memory bus is not granted for several
+     * peripheral requests.
+     * - In the memory-to-peripheral mode, an underrun condition may
+     * occur if the memory bus has not been granted before a
+     * peripheral request occurs.
+     */
+    bool enableFifoErrorInterrupt = false;
+
+    /**
+     * Direct mode is the default fifo operating mode.
+     *
+     * Direct mode error can only be set in the peripheral-to-memory
+     * mode while operating in direct mode. This flag is set when a DMA request
+     * occurs while the previous data have not yet been fully transferred into
+     * the memory (because the memory bus was not granted). In this case, the
+     * flag indicates that 2 data items were be transferred successively to the
+     * same destination address, which could be an issue if the destination is
+     * not able to manage this situation.
+     */
+    bool enableDirectModeErrorInterrupt = false;
+};
+
+// Forward declaration
+class DMAStream;
+class DMAStreamGuard;
+
+/**
+ * @brief This class is responsible for streams acquisition,
+ * streams release and interrupt handling.
+ */
+class DMADriver
+{
+public:
+    // cppcheck-suppress  noExplicitConstructor
+
+    void IRQhandleInterrupt(DMADefs::DMAStreamId id);
+
+    static DMADriver& instance();
+
+    /**
+     * @return True if the stream is not already in use.
+     */
+    bool tryStream(DMADefs::DMAStreamId id);
+
+    /**
+     * @brief Try to acquire the specified stream and initialize it with the
+     * correct channel.
+     * @param id The id of the stream to be acquired.
+     * @param channel The channel used to initialize the stream.
+     * @param timeout The maximum time that will be waited, defaults to waiting
+     * forever.
+     * @return A stream guard that might be valid or not, depending on the
+     * outcome of the request.
+     */
+    DMAStreamGuard acquireStream(
+        DMADefs::DMAStreamId id, DMADefs::Channel channel,
+        std::chrono::nanoseconds timeout = std::chrono::nanoseconds::zero());
+
+    /**
+     * @brief Try to acquire a stream that is connected to the specified
+     * peripheral.
+     * @param peripheral The wanted peripheral.
+     * @param timeout The maximum time that will be waited, defaults to waiting
+     * forever.
+     * @return A stream guard that might be valid or not, depending on the
+     * outcome of the request.
+     */
+    DMAStreamGuard acquireStreamForPeripheral(
+        DMADefs::Peripherals peripheral,
+        std::chrono::nanoseconds timeout = std::chrono::nanoseconds::zero());
+
+    void releaseStream(DMADefs::DMAStreamId id);
+
+private:
+    DMADriver();
+
+    /**
+     * @brief Wakeup the sleeping thread associated to the stream.
+     */
+    void IRQwakeupThread(DMAStream& stream);
+
+    miosix::FastMutex mutex;
+    miosix::ConditionVariable cv;
+    std::map<DMADefs::DMAStreamId, DMAStream> streams;
+
+public:
+    DMADriver(const DMADriver&)            = delete;
+    DMADriver& operator=(const DMADriver&) = delete;
+};
+
+/**
+ * @brief This class represents the actual DMA stream.
+ * It can be used to setup, start and stop DMA transactions.
+ */
+class DMAStream
+{
+    friend DMADriver;
+
+public:
+    // cppcheck-suppress  noExplicitConstructor
+
+    /**
+     * @brief Setup the stream with the given configuration.
+     */
+    void setup(DMATransaction& transaction);
+
+    /**
+     * @brief Activate the stream. As soon as the stream is enabled, it
+     * serves any DMA request from/to the peripheral connected to the stream.
+     */
+    void enable();
+
+    /**
+     * @brief Stop the DMA transaction (if running).
+     * This is equivalent to killing the transaction: DO NOT expect to be able
+     * to restart the transaction from where it was interrupted. The work
+     * completed up to the call will still be valid.
+     * @warning If set, the transfer complete interrupt will be fired.
+     */
+    void disable();
+
+    /**
+     * @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. Cache invalidation must be handled by the
+     * user.
+     */
+    void waitForHalfTransfer();
+
+    /**
+     * @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();
+
+    /**
+     * @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.
+     * @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. Cache invalidation must be handled by the
+     * user.
+     */
+    bool timedWaitForHalfTransfer(std::chrono::nanoseconds timeout_ns);
+
+    /**
+     * @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.
+     */
+    bool timedWaitForTransferComplete(std::chrono::nanoseconds timeout_ns);
+
+    void setHalfTransferCallback(std::function<void()> callback);
+
+    void resetHalfTransferCallback();
+
+    void setTransferCompleteCallback(std::function<void()> callback);
+
+    void resetTransferCompleteCallback();
+
+    void setErrorCallback(std::function<void()> callback);
+
+    void resetErrorCallback();
+
+    /**
+     * @brief Reads the current flags status.
+     *
+     * The values can be read with the get***FlagStatus functions.
+     */
+    void readFlags();
+
+    /**
+     * @brief Set the number of bytes to be exchanged during a
+     * dma transaction. Useful in case you don't want to change
+     * the entire configuration. Use while the stream is not
+     * enabled.
+     * @return True if the operation succeeded, false otherwise.
+     */
+    bool setNumberOfDataItems(const uint16_t nBytes);
+
+    /**
+     * @brief Select the channel to be used by the stream during
+     * the transactions.
+     */
+    void setChannel(const DMADefs::Channel channel);
+
+    /**
+     * @brief Returns the last read status of the half transfer flag.
+     */
+    inline bool getHalfTransferFlagStatus() { return halfTransferFlag; }
+
+    /**
+     * @brief Returns the last read status of the transfer complete flag.
+     */
+    inline bool getTransferCompleteFlagStatus() { return transferCompleteFlag; }
+
+    /**
+     * @brief Returns the last read status of the transfer error flag.
+     */
+    inline bool getTransferErrorFlagStatus() { return transferErrorFlag; }
+
+    /**
+     * @brief Returns the last read status of the fifo error flag.
+     */
+    inline bool getFifoErrorFlagStatus() { return fifoErrorFlag; }
+
+    /**
+     * @brief Returns the last read status of the direct mode (default
+     * mode) error flag.
+     */
+    inline bool getDirectModeErrorFlagStatus() { return directModeErrorFlag; }
+
+    /**
+     * @brief Returns the number of the buffer currently in use when
+     * in double buffer mode.
+     * @return 1 or 2 depending on the buffer currently in use.
+     */
+    int getCurrentBufferNumber();
+
+    inline DMADefs::DMAStreamId getStreamId() { return id; }
+
+    inline DMADefs::Channel getCurrentChannel() { return currentChannel; }
+
+    inline void clearHalfTransferFlag()
+    {
+        *IFCR |= DMA_LIFCR_CHTIF0 << IFindex;
+    }
+
+    inline void clearTransferCompleteFlag()
+    {
+        *IFCR |= DMA_LIFCR_CTCIF0 << IFindex;
+    }
+
+    inline void clearTransferErrorFlag()
+    {
+        *IFCR |= DMA_LIFCR_CTEIF0 << IFindex;
+    }
+
+    inline void clearFifoErrorFlag() { *IFCR |= DMA_LIFCR_CFEIF0 << IFindex; }
+
+    inline void clearDirectModeErrorFlag()
+    {
+        *IFCR |= DMA_LIFCR_CDMEIF0 << IFindex;
+    }
+
+    /**
+     * @brief Clear all the flags for the selected stream in the DMA ISR
+     * register (LISR or HISR depending on the selected stream id).
+     */
+    inline void clearAllFlags()
+    {
+        *IFCR |= (DMA_LIFCR_CHTIF0 | DMA_LIFCR_CTCIF0 | DMA_LIFCR_CTEIF0 |
+                  DMA_LIFCR_CFEIF0 | DMA_LIFCR_CDMEIF0)
+                 << IFindex;
+    }
+
+private:
+    DMAStream(DMADefs::DMAStreamId id, DMADefs::Channel channel);
+
+    DMATransaction currentSetup;
+
+    /**
+     * @brief Used to determine if the user thread is
+     * waiting to be awakened by an interrupt.
+     */
+    miosix::Thread* waitingThread = nullptr;
+
+    // These flags are set by the interrupt routine and tells the user
+    // which event were triggered
+    bool halfTransferFlag     = false;
+    bool transferCompleteFlag = false;
+    bool transferErrorFlag    = false;
+    bool fifoErrorFlag        = false;
+    bool directModeErrorFlag  = false;
+
+    std::function<void()> halfTransferCallback;
+    std::function<void()> transferCompleteCallback;
+    std::function<void()> errorCallback;
+
+    const DMADefs::DMAStreamId id;
+    DMADefs::Channel currentChannel;
+    DMA_Stream_TypeDef* registers;
+
+    volatile uint32_t* ISR;   ///< Interrupt status register
+    volatile uint32_t* IFCR;  ///< Interrupt flags clear register
+    int IFindex;              ///< Interrupt flags index
+
+    inline bool waitForInterruptEventImpl(
+        bool isInterruptEnabled, std::function<bool()> getEventStatus,
+        std::function<void()> clearEventStatus, bool& eventTriggered,
+        long long timeout_ns)
+    {
+        // Return value: true if the event was triggered, false if the timeout
+        // expired
+        bool result = false;
+
+        // If the interrupt is enabled we can just pause and wait for it.
+        // Otherwise we need to pool the flag.
+        if (isInterruptEnabled)
+        {
+            // Here we have 2 cases:
+            // - This function has been called after the interrupt fired. In
+            // this case the interrupt saves the flags status and we check them
+            // - The interrupt has not yet fired. We pause the thread and the
+            // interrupt will wake us up
+
+            if (eventTriggered)
+            {
+                result = true;
+            }
+            else
+            {
+                // Save the current thread pointer
+                waitingThread = miosix::Thread::getCurrentThread();
+
+                // Wait until the thread is woken up and the pointer is cleared
+                miosix::FastInterruptDisableLock dLock;
+                if (timeout_ns >= 0)
+                {
+                    do
+                    {
+                        if (miosix::Thread::IRQenableIrqAndTimedWait(
+                                dLock, timeout_ns + miosix::getTime()) ==
+                            miosix::TimedWaitResult::Timeout)
+                        {
+                            result = false;
+
+                            // If the timeout expired we clear the thread
+                            // pointer so that the interrupt, if it will occur,
+                            // will not wake up the thread (and we can exit the
+                            // while loop)
+                            waitingThread = nullptr;
+                        }
+                        else
+                        {
+                            result = true;
+                        }
+                    } while (waitingThread);
+                }
+                else
+                {
+                    do
+                    {
+                        miosix::Thread::IRQenableIrqAndWait(dLock);
+                    } while (waitingThread);
+                    result = true;
+                }
+            }
+
+            // Before returning we need to clear the flags otherwise we could
+            // get misfires
+            eventTriggered = false;
+        }
+        else
+        {
+            // Pool the flag if the user did not enable the interrupt
+            if (timeout_ns >= 0)
+            {
+                const long long start = miosix::getTime();
+
+                do
+                {
+                    readFlags();
+                } while (!getEventStatus() &&
+                         miosix::getTime() - start < timeout_ns);
+
+                result = getEventStatus();
+            }
+            else
+            {
+                while (!getEventStatus())
+                    readFlags();
+                result = true;
+            }
+
+            if (result)
+            {
+                // Clear the flag
+                clearEventStatus();
+            }
+        }
+
+        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;
+
+    DMAStream(DMAStream&&) noexcept            = default;
+    DMAStream& operator=(DMAStream&&) noexcept = default;
+};
+
+/**
+ * @brief Simple RAII class to handle DMA streams.
+ */
+class DMAStreamGuard
+{
+public:
+    // cppcheck-suppress  noExplicitConstructor
+    DMAStreamGuard(DMAStream* ptr) : pStream(ptr) {}
+
+    ~DMAStreamGuard()
+    {
+        if (pStream != nullptr)
+            DMADriver::instance().releaseStream(pStream->getStreamId());
+    }
+
+    DMAStreamGuard(const DMAStreamGuard&)            = delete;
+    DMAStreamGuard& operator=(const DMAStreamGuard&) = delete;
+
+    DMAStreamGuard(DMAStreamGuard&&) noexcept            = default;
+    DMAStreamGuard& operator=(DMAStreamGuard&&) noexcept = default;
+
+    DMAStream* operator->();
+
+    /**
+     * @return True if the stream was correctly allocated and
+     * is ready to use. False otherwise.
+     */
+    inline bool isValid() { return pStream != nullptr; }
+
+private:
+    DMAStream* const pStream;
+};
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/dma/DMADefs.cpp b/src/shared/drivers/dma/DMADefs.cpp
new file mode 100644
index 000000000..6289a7a1a
--- /dev/null
+++ b/src/shared/drivers/dma/DMADefs.cpp
@@ -0,0 +1,69 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * 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 "DMADefs.h"
+
+// Include board mappings
+#if defined(STM32F407xx)
+#include "board_mappings/stm32f407xx_mappings.cpp"
+
+#elif defined(STM32F429xx)
+#include "board_mappings/stm32f429xx_mappings.cpp"
+
+#elif defined(STM32F767xx)
+#include "board_mappings/stm32f767xx_mappings.cpp"
+
+#else
+#warning \
+    "DMA: mapping not supported for this board! An empty mapping will be used, acquireStreamForPeripheral() will not work!"
+namespace Boardcore
+{
+namespace DMADefs
+{
+const std::multimap<Peripherals, std::pair<DMAStreamId, Channel> >
+    mapPeripherals = {};
+}  // namespace DMADefs
+}  // namespace Boardcore
+
+#endif
+
+// Check that at most 1 board is defined
+#if defined(STM32F407xx) + defined(STM32F429xx) + defined(STM32F767xx) > 1
+#error "DMA: Multiple boards defined. Only one must be defined"
+#endif
+
+namespace Boardcore
+{
+
+namespace DMADefs
+{
+
+const IRQn_Type irqNumberMapping[] = {
+    DMA1_Stream0_IRQn, DMA1_Stream1_IRQn, DMA1_Stream2_IRQn, DMA1_Stream3_IRQn,
+    DMA1_Stream4_IRQn, DMA1_Stream5_IRQn, DMA1_Stream6_IRQn, DMA1_Stream7_IRQn,
+    DMA2_Stream0_IRQn, DMA2_Stream1_IRQn, DMA2_Stream2_IRQn, DMA2_Stream3_IRQn,
+    DMA2_Stream4_IRQn, DMA2_Stream5_IRQn, DMA2_Stream6_IRQn, DMA2_Stream7_IRQn,
+};
+
+}  // namespace DMADefs
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/dma/DMADefs.h b/src/shared/drivers/dma/DMADefs.h
new file mode 100644
index 000000000..2b2d9fe3e
--- /dev/null
+++ b/src/shared/drivers/dma/DMADefs.h
@@ -0,0 +1,257 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * 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 <interfaces/arch_registers.h>
+#include <stdint.h>
+
+#include <map>
+
+namespace Boardcore
+{
+
+// For some reason these defines are missing
+// in the CMSIS STM32F4xx file
+#if defined(STM32F407xx) || defined(STM32F429xx)
+
+#ifndef DMA_SxCR_MSIZE_Pos
+#define DMA_SxCR_MSIZE_Pos (13U)
+#endif
+
+#ifndef DMA_SxCR_PSIZE_Pos
+#define DMA_SxCR_PSIZE_Pos (11U)
+#endif
+
+#endif  // STM32F407xx
+
+namespace DMADefs
+{
+
+enum class DMAStreamId : uint8_t
+{
+    /**
+     * Here are defined the selectable streams.
+     *
+     * The problem is that some of these stream are used
+     * by miosix. The corresponding IRQHandlers are already defined
+     * in there, causing conflicts.
+     * Moreover, the used streams might differ from different boards.
+     * That's why some streams are available only for a particular
+     * board.
+     */
+
+    DMA1_Str0 = 0,
+
+#ifndef STM32F407xx
+    // This stream is used by miosix for STM32F407xx boards
+    DMA1_Str1 = 1,
+#endif  // STM32F407xx
+
+    DMA1_Str2 = 2,
+
+#ifndef STM32F407xx
+    // This stream is used by miosix for STM32F407xx boards
+    DMA1_Str3 = 3,
+#endif  // STM32F407xx
+
+    DMA1_Str4 = 4,
+    DMA1_Str5 = 5,
+    DMA1_Str6 = 6,
+    DMA1_Str7 = 7,
+    DMA2_Str0 = 8,
+    DMA2_Str1 = 9,
+    DMA2_Str2 = 10,
+    // DMA2_Str3 = 11, // Always used by miosix on currently supported boards
+    DMA2_Str4 = 12,
+
+#if !defined(STM32F767xx) && !defined(STM32F429xx)
+    // This stream is used by miosix for STM32F767xx
+    // and STM32F429xx boards
+    DMA2_Str5 = 13,
+#endif  // STM32F767xx & STM32F429xx
+
+    DMA2_Str6 = 14,
+
+#if !defined(STM32F767xx) && !defined(STM32F429xx)
+    // This stream is used by miosix for STM32F767xx
+    // and STM32F429xx boards
+    DMA2_Str7 = 15,
+#endif  // STM32F767xx & STM32F429xx
+};
+
+/**
+ * @brief Channels selectable for each dma stream.
+ */
+enum class Channel : uint32_t
+{
+    // The first 8 channels are valid for all supported architectures
+    CHANNEL0 = 0,
+    CHANNEL1 = DMA_SxCR_CHSEL_0,
+    CHANNEL2 = DMA_SxCR_CHSEL_1,
+    CHANNEL3 = DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0,
+    CHANNEL4 = DMA_SxCR_CHSEL_2,
+    CHANNEL5 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_0,
+    CHANNEL6 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1,
+    CHANNEL7 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0,
+
+// stm32f767 also has channels from 8 to 11
+#ifdef STM32F767xx
+    CHANNEL8  = DMA_SxCR_CHSEL_3,
+    CHANNEL9  = DMA_SxCR_CHSEL_3 | DMA_SxCR_CHSEL_0,
+    CHANNEL10 = DMA_SxCR_CHSEL_3 | DMA_SxCR_CHSEL_1,
+    CHANNEL11 = DMA_SxCR_CHSEL_3 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0,
+#endif  // STM32F767xx
+};
+
+/**
+ * @brief All the peripherals connected to dma.
+ */
+enum class Peripherals : uint8_t
+{
+    PE_MEM_ONLY,
+
+    PE_SPI1_TX,
+    PE_SPI1_RX,
+    PE_SPI2_TX,
+    PE_SPI2_RX,
+    PE_SPI3_TX,
+    PE_SPI3_RX,
+    PE_SPI4_TX,
+    PE_SPI4_RX,
+    PE_SPI5_TX,
+    PE_SPI5_RX,
+    PE_SPI6_TX,
+    PE_SPI6_RX,
+    PE_QUADSPI,
+    PE_USART1_TX,
+    PE_USART1_RX,
+    PE_USART2_TX,
+    PE_USART2_RX,
+    PE_USART3_TX,
+    PE_USART3_RX,
+    PE_UART4_TX,
+    PE_UART4_RX,
+    PE_UART5_TX,
+    PE_UART5_RX,
+    PE_USART6_TX,
+    PE_USART6_RX,
+    PE_UART7_TX,
+    PE_UART7_RX,
+    PE_UART8_TX,
+    PE_UART8_RX,
+    PE_I2C1_TX,
+    PE_I2C1_RX,
+    PE_I2C2_TX,
+    PE_I2C2_RX,
+    PE_I2C3_TX,
+    PE_I2C3_RX,
+    PE_I2C4_TX,
+    PE_I2C4_RX,
+    PE_I2S2_EXT_TX,
+    PE_I2S2_EXT_RX,
+    PE_I2S3_EXT_TX,
+    PE_I2S3_EXT_RX,
+    PE_TIM1_UP,
+    PE_TIM1_TRIG,
+    PE_TIM1_COM,
+    PE_TIM1_CH1,
+    PE_TIM1_CH2,
+    PE_TIM1_CH3,
+    PE_TIM1_CH4,
+    PE_TIM2_UP,
+    PE_TIM2_CH1,
+    PE_TIM2_CH2,
+    PE_TIM2_CH3,
+    PE_TIM2_CH4,
+    PE_TIM3_UP,
+    PE_TIM3_TRIG,
+    PE_TIM3_CH1,
+    PE_TIM3_CH2,
+    PE_TIM3_CH3,
+    PE_TIM3_CH4,
+    PE_TIM4_UP,
+    PE_TIM4_CH1,
+    PE_TIM4_CH2,
+    PE_TIM4_CH3,
+    PE_TIM5_UP,
+    PE_TIM5_TRIG,
+    PE_TIM5_CH1,
+    PE_TIM5_CH2,
+    PE_TIM5_CH3,
+    PE_TIM5_CH4,
+    PE_TIM6_UP,
+    PE_TIM7_UP,
+    PE_TIM8_UP,
+    PE_TIM8_TRIG,
+    PE_TIM8_COM,
+    PE_TIM8_CH1,
+    PE_TIM8_CH2,
+    PE_TIM8_CH3,
+    PE_TIM8_CH4,
+    PE_DAC1,
+    PE_DAC2,
+    PE_ADC1,
+    PE_ADC2,
+    PE_ADC3,
+    PE_SAI1_A,
+    PE_SAI2_A,
+    PE_SAI1_B,
+    PE_SAI2_B,
+    PE_DCMI,
+    PE_SDIO,
+    PE_SDMMC1,
+    PE_SDMMC2,
+    PE_CRYP_OUT,
+    PE_CRYP_IN,
+    PE_HASH_IN,
+    PE_SPDIFRX_DT,
+    PE_SPDIFRX_CS,
+    PE_DFSDM1_FLT0,
+    PE_DFSDM1_FLT1,
+    PE_DFSDM1_FLT2,
+    PE_DFSDM1_FLT3,
+    PE_JPEG_IN,
+    PE_JPEG_OUT,
+};
+
+/**
+ * @brief Mapping between `DMAStreamId` and the corresponding irq number.
+ * This is needed because irq number values are not contiguous and they are
+ * architecture dependent.
+ */
+extern const IRQn_Type irqNumberMapping[];
+
+/**
+ * @brief Maps the peripherals to the dma streams (and
+ * the corresponding channel) that are connected with.
+ *
+ * The actual initialization of mapPeripherals is board
+ * specific, and can be found inside the "board_mappings"
+ * folder.
+ */
+extern const std::multimap<Peripherals, std::pair<DMAStreamId, Channel>>
+    mapPeripherals;
+
+}  // namespace DMADefs
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/dma/DMAStream.h b/src/shared/drivers/dma/DMAStream.h
deleted file mode 100644
index ad3fd03b8..000000000
--- a/src/shared/drivers/dma/DMAStream.h
+++ /dev/null
@@ -1,510 +0,0 @@
-/* Copyright (c) 2021 Skyward Experimental Rocketry
- * Author: Alberto Nidasio
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#pragma once
-
-#include <diagnostic/PrintLogger.h>
-#include <interfaces/arch_registers.h>
-
-namespace Boardcore
-{
-
-/**
- * @brief Driver for STM32 DMA streams.
- *
- * Direct Memory Access is used in order to provide high-speed data transfer
- * between peripherals and memory and betweeen memory and memory. Data can be
- * quickly moved by DMA without any CPU interacion.
- *
- * The STM32F4 family features two DMA controller, each with 8 streams.
- *
- * DMA features are:
- * - Four-word depth 32 FIFO memory buffers per stream, that can be used in FIFO
- * mode or direct mode
- * - Double buffering
- * - Software programmable piorities between DMA stream requests (4 levels)
- * - The number of data items to be transferred can be managed either by the DMA
- * controller or by the peripheral:
- *   - DMA flow controller: the number of data items to be transferred is
- * software-programmable form 1 to 65535
- *   - Peripheral flow controller: the number of data items to be transferred is
- * unknown and controlled by the source or the destination peripheral that
- * signals the end of the transfer by hardware
- * - Incrementing or non incrementing adressing for source and destination
- * - Circular buffer management
- *
- * Note that only DMA2 controller can perform memory-to-memory transactions.
- *
- * Each DMA transfer consists of three operations:
- * - A loading from the peripheral data register or a location in memory
- * - A storage of the data loaded to the peripheral data register or a location
- * in memory
- * - A post-decrement of the counter of transeffer data items (NDTR)
- *
- * After an event, the peripheral sends a request signal to the DMA controller.
- * The DMA controller serves the request depending on the channel priorities. As
- * soon as the DMA controller accesses the peripheral, an acknowledge signal is
- * sent to the peripheral by the DMA controller. The peripheral releases its
- * request as soon as it gets the acknowledge signal.
- *
- * Each stream is associated with a DMA request that can be selected out of 8
- * possible channel requests.
- *
- * Note that configuration functions have effect only if the stream is disabled.
- */
-class DMAStream
-{
-public:
-    enum class Channel : uint32_t
-    {
-        CHANNEL0 = 0,
-        CHANNEL1 = DMA_SxCR_CHSEL_0,
-        CHANNEL2 = DMA_SxCR_CHSEL_1,
-        CHANNEL3 = DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0,
-        CHANNEL4 = DMA_SxCR_CHSEL_2,
-        CHANNEL5 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_0,
-        CHANNEL6 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1,
-        CHANNEL7 = DMA_SxCR_CHSEL,
-    };
-
-    enum class MemoryBurstConfiguration : uint32_t
-    {
-        SINGLE_TRANSFER = 0,
-        INCR4           = DMA_SxCR_MBURST_0,
-        INCR8           = DMA_SxCR_MBURST_1,
-        INCR16          = DMA_SxCR_MBURST,
-    };
-
-    enum class PeripheralBurstConfiguration : uint32_t
-    {
-        SINGLE_TRANSFER = 0,
-        INCR4           = DMA_SxCR_PBURST_0,
-        INCR8           = DMA_SxCR_PBURST_1,
-        INCR16          = DMA_SxCR_PBURST,
-    };
-
-    enum class PriorityLevel : uint32_t
-    {
-        LOW       = 0,
-        MEDIUM    = DMA_SxCR_PL_0,
-        HIGH      = DMA_SxCR_PL_1,
-        VERY_HIGH = DMA_SxCR_PL,
-    };
-
-    enum class MemoryDataSize : uint32_t
-    {
-        BYTE      = 0,
-        HALF_WORD = DMA_SxCR_MSIZE_0,
-        WORD      = DMA_SxCR_MSIZE_1
-    };
-
-    enum class PeripheralDataSize : uint32_t
-    {
-        BYTE      = 0,
-        HALF_WORD = DMA_SxCR_PSIZE_0,
-        WORD      = DMA_SxCR_PSIZE_1
-    };
-
-    enum class DataTransferDirection : uint32_t
-    {
-        PERIPH_TO_MEM = 0,
-        MEM_TO_PERIPH = DMA_SxCR_DIR_0,
-        MEM_TO_MEM    = DMA_SxCR_DIR_1,
-    };
-
-    DMAStream(DMA_Stream_TypeDef* dmaStream);
-
-    DMA_TypeDef* getController();
-
-    DMA_Stream_TypeDef* getStream();
-
-    void reset();
-
-    void enable();
-
-    void disable();
-
-    void setStreamChannel(Channel channel);
-
-    void setStreamMemoryBurstConfiguration(MemoryBurstConfiguration config);
-
-    void setStreamPeripheralBurstConfiguration(
-        PeripheralBurstConfiguration config);
-
-    void enableDoubleBufferMode();
-
-    void disableDoubleBufferMode();
-
-    void setStreamPriorityLevel(PriorityLevel priorityLevel);
-
-    void setMemoryDataSize(MemoryDataSize size);
-
-    void setPeripheralDataSize(PeripheralDataSize size);
-
-    void enableMemoryIncrement();
-
-    void disableMemoryIncrement();
-
-    void enablePeripheralIncrement();
-
-    void disablePeripheralIncrement();
-
-    void enableCircularMode();
-
-    void disableCircularMode();
-
-    void setDataTransferDirection(DataTransferDirection direction);
-
-    void setPeripheralFlowController();
-
-    void setDMAFlowController();
-
-    void disableTransferCompleteInterrupt();
-
-    void enableTransferCompleteInterrupt();
-
-    void enableHalfTransferCompleteInterrupt();
-
-    void disableHalfTransferCompleteInterrupt();
-
-    void enableTransferErrorInterrupt();
-
-    void disableTransferErrorInterrupt();
-
-    void enableDirectModeErrorInterrupt();
-
-    void disableDirectModeErrorInterrupt();
-
-    /**
-     * @brief Sets the number of data items to transfer.
-     *
-     * The NDTR register decrements after each DMA transfer. Once the transfer
-     * has completed, this register can either stay at zero (when the stream is
-     * in normal mode) or be reloaded automatically with the previusly
-     * programmed value in the following cases:
-     * - When the stream is configure in circular mode
-     * - When the stream is enabled again (by setting EN bit to 1)
-     * If the value of the NDTR register is zero, no transaction can be served
-     * even if the stream is enabled.
-     */
-    void setNumberOfDataItems(uint16_t numberOfDataItems);
-
-    uint16_t readNumberOfDataItems();
-
-    void setPeripheralAddress(uint32_t* address);
-
-    void setMemory0Address(uint32_t* address);
-
-    /**
-     * @brief Sets the memory address used only in double buffer mode.
-     */
-    void setMemory1Address(uint32_t* address);
-
-    void clearStatusRegister();
-
-private:
-    DMA_TypeDef* dmaController;
-    DMA_Stream_TypeDef* dmaStream;
-
-    // Interrupt status flags
-    volatile uint32_t* IFCR;  ///< Interrupt flags clear register
-    uint32_t IFCR_MASK;       ///< Clear mask for all interrupt flags
-
-    PrintLogger logger = Logging::getLogger("DMAStream");
-};
-
-inline DMAStream::DMAStream(DMA_Stream_TypeDef* dmaStream)
-    : dmaStream(dmaStream)
-{
-    // Find the correct DMA controller
-    if (reinterpret_cast<uint32_t*>(dmaStream) < &(DMA2->LISR))
-        dmaController = DMA1;
-    else
-        dmaController = DMA2;
-
-    // Find the corret interrupt flags clear register
-    if (dmaController == DMA1)
-        if (dmaStream <= DMA1_Stream3)
-            IFCR = &(DMA1->LIFCR);
-        else
-            IFCR = &(DMA1->HIFCR);
-    else if (dmaStream <= DMA2_Stream3)
-        IFCR = &(DMA2->LIFCR);
-    else
-        IFCR = &(DMA2->HIFCR);
-
-    // Find the correct clear mask
-    if (dmaStream == DMA1_Stream0 || dmaStream == DMA2_Stream0)
-    {
-        IFCR_MASK = DMA_LIFCR_CFEIF0 | DMA_LIFCR_CHTIF0 | DMA_LIFCR_CTCIF0 |
-                    DMA_LIFCR_CTEIF0 | DMA_LIFCR_CDMEIF0;
-    }
-    else if (dmaStream == DMA1_Stream1 || dmaStream == DMA2_Stream1)
-    {
-        IFCR_MASK = DMA_LIFCR_CFEIF1 | DMA_LIFCR_CHTIF1 | DMA_LIFCR_CTCIF1 |
-                    DMA_LIFCR_CTEIF1 | DMA_LIFCR_CDMEIF1;
-    }
-    else if (dmaStream == DMA1_Stream2 || dmaStream == DMA2_Stream2)
-    {
-        IFCR_MASK = DMA_LIFCR_CFEIF2 | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTCIF2 |
-                    DMA_LIFCR_CTEIF2 | DMA_LIFCR_CDMEIF2;
-    }
-    else if (dmaStream == DMA1_Stream3 || dmaStream == DMA2_Stream3)
-    {
-        IFCR_MASK = DMA_LIFCR_CFEIF3 | DMA_LIFCR_CHTIF3 | DMA_LIFCR_CTCIF3 |
-                    DMA_LIFCR_CTEIF3 | DMA_LIFCR_CDMEIF3;
-    }
-    else if (dmaStream == DMA1_Stream4 || dmaStream == DMA2_Stream4)
-    {
-        IFCR_MASK = DMA_HIFCR_CFEIF4 | DMA_HIFCR_CHTIF4 | DMA_HIFCR_CTCIF4 |
-                    DMA_HIFCR_CTEIF4 | DMA_HIFCR_CDMEIF4;
-    }
-    else if (dmaStream == DMA1_Stream5 || dmaStream == DMA2_Stream5)
-    {
-        IFCR_MASK = DMA_HIFCR_CFEIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTCIF5 |
-                    DMA_HIFCR_CTEIF5 | DMA_HIFCR_CDMEIF5;
-    }
-    else if (dmaStream == DMA1_Stream6 || dmaStream == DMA2_Stream6)
-    {
-        IFCR_MASK = DMA_HIFCR_CFEIF6 | DMA_HIFCR_CHTIF6 | DMA_HIFCR_CTCIF6 |
-                    DMA_HIFCR_CTEIF6 | DMA_HIFCR_CDMEIF6;
-    }
-    else if (dmaStream == DMA1_Stream7 || dmaStream == DMA2_Stream7)
-    {
-        IFCR_MASK = DMA_HIFCR_CFEIF7 | DMA_HIFCR_CHTIF7 | DMA_HIFCR_CTCIF7 |
-                    DMA_HIFCR_CTEIF7 | DMA_HIFCR_CDMEIF7;
-    }
-    else
-    {
-        IFCR_MASK = 0;
-        LOG_CRIT(logger, "Could not recognize DMA stream");
-    }
-}
-
-inline DMA_TypeDef* DMAStream::getController() { return dmaController; }
-
-inline DMA_Stream_TypeDef* DMAStream::getStream() { return dmaStream; }
-
-inline void DMAStream::reset()
-{
-    // Disable stream
-    dmaStream->CR &= ~DMA_SxCR_EN;
-
-    // Wait for the stream to be disabled
-    while (dmaStream->CR & DMA_SxCR_EN)
-        ;
-
-    // Clear the registers
-    dmaStream->CR  = 0;
-    dmaStream->FCR = 0;
-    clearStatusRegister();
-
-    // Wait for the stream to be disabled
-    while (dmaStream->CR & DMA_SxCR_EN)
-        ;
-}
-
-inline void DMAStream::enable()
-{
-    // Before enabling the stream ensures all status flags are cleared
-    clearStatusRegister();
-
-    // Enable the stream
-    dmaStream->CR |= DMA_SxCR_EN;
-}
-
-inline void DMAStream::disable() { dmaStream->CR &= ~DMA_SxCR_EN; }
-
-inline void DMAStream::setStreamChannel(Channel channel)
-{
-    // First clear the configuration
-    dmaStream->CR &= ~DMA_SxCR_CHSEL;
-
-    // Set the new value
-    dmaStream->CR |= static_cast<uint32_t>(channel);
-}
-
-inline void DMAStream::setStreamMemoryBurstConfiguration(
-    MemoryBurstConfiguration config)
-{
-    // First clear the configuration
-    dmaStream->CR &= ~DMA_SxCR_MBURST;
-
-    // Set the new value
-    dmaStream->CR |= static_cast<uint32_t>(config);
-}
-
-inline void DMAStream::setStreamPeripheralBurstConfiguration(
-    PeripheralBurstConfiguration config)
-{
-    // First clear the configuration
-    dmaStream->CR &= ~DMA_SxCR_PBURST;
-
-    // Set the new value
-    dmaStream->CR |= static_cast<uint32_t>(config);
-}
-
-inline void DMAStream::enableDoubleBufferMode()
-{
-    dmaStream->CR |= DMA_SxCR_DBM;
-}
-
-inline void DMAStream::disableDoubleBufferMode()
-{
-    dmaStream->CR &= ~DMA_SxCR_DBM;
-}
-
-inline void DMAStream::setStreamPriorityLevel(PriorityLevel priorityLevel)
-{
-    // First clear the configuration
-    dmaStream->CR &= ~DMA_SxCR_PL;
-
-    // Set the new value
-    dmaStream->CR |= static_cast<uint32_t>(priorityLevel);
-}
-
-inline void DMAStream::setMemoryDataSize(MemoryDataSize size)
-{
-    // First clear the configuration
-    dmaStream->CR &= ~DMA_SxCR_MSIZE;
-
-    // Set the new value
-    dmaStream->CR |= static_cast<uint32_t>(size);
-}
-
-inline void DMAStream::setPeripheralDataSize(PeripheralDataSize size)
-{
-    // First clear the configuration
-    dmaStream->CR &= ~DMA_SxCR_PSIZE;
-
-    // Set the new value
-    dmaStream->CR |= static_cast<uint32_t>(size);
-}
-
-inline void DMAStream::enableMemoryIncrement()
-{
-    dmaStream->CR |= DMA_SxCR_MINC;
-}
-
-inline void DMAStream::disableMemoryIncrement()
-{
-    dmaStream->CR &= ~DMA_SxCR_MINC;
-}
-
-inline void DMAStream::enablePeripheralIncrement()
-{
-    dmaStream->CR |= DMA_SxCR_PINC;
-}
-
-inline void DMAStream::disablePeripheralIncrement()
-{
-    dmaStream->CR &= ~DMA_SxCR_PINC;
-}
-
-inline void DMAStream::enableCircularMode() { dmaStream->CR |= DMA_SxCR_CIRC; }
-
-inline void DMAStream::disableCircularMode() { dmaStream->CR |= DMA_SxCR_CIRC; }
-
-inline void DMAStream::setDataTransferDirection(DataTransferDirection direction)
-{
-    // First clear the configuration
-    dmaStream->CR &= ~DMA_SxCR_DIR;
-
-    // Set the new value
-    dmaStream->CR |= static_cast<uint32_t>(direction);
-}
-
-inline void DMAStream::setPeripheralFlowController()
-{
-    dmaStream->CR |= DMA_SxCR_PFCTRL;
-}
-
-inline void DMAStream::setDMAFlowController()
-{
-    dmaStream->CR &= ~DMA_SxCR_PFCTRL;
-}
-
-inline void DMAStream::enableTransferCompleteInterrupt()
-{
-    dmaStream->CR |= DMA_SxCR_TCIE;
-}
-
-inline void DMAStream::disableTransferCompleteInterrupt()
-{
-    dmaStream->CR &= ~DMA_SxCR_TCIE;
-}
-
-inline void DMAStream::enableHalfTransferCompleteInterrupt()
-{
-    dmaStream->CR |= DMA_SxCR_HTIE;
-}
-
-inline void DMAStream::disableHalfTransferCompleteInterrupt()
-{
-    dmaStream->CR &= ~DMA_SxCR_HTIE;
-}
-
-inline void DMAStream::enableTransferErrorInterrupt()
-{
-    dmaStream->CR |= DMA_SxCR_TEIE;
-}
-
-inline void DMAStream::disableTransferErrorInterrupt()
-{
-    dmaStream->CR &= ~DMA_SxCR_TEIE;
-}
-
-inline void DMAStream::enableDirectModeErrorInterrupt()
-{
-    dmaStream->CR |= DMA_SxCR_DMEIE;
-}
-
-inline void DMAStream::disableDirectModeErrorInterrupt()
-{
-    dmaStream->CR &= ~DMA_SxCR_DMEIE;
-}
-
-inline void DMAStream::setNumberOfDataItems(uint16_t numberOfDataItems)
-{
-    dmaStream->NDTR = numberOfDataItems;
-}
-
-inline uint16_t DMAStream::readNumberOfDataItems() { return dmaStream->NDTR; }
-
-inline void DMAStream::setPeripheralAddress(uint32_t* address)
-{
-    dmaStream->PAR = reinterpret_cast<uint32_t>(address);
-}
-
-inline void DMAStream::setMemory0Address(uint32_t* address)
-{
-    dmaStream->M0AR = reinterpret_cast<uint32_t>(address);
-}
-
-inline void DMAStream::setMemory1Address(uint32_t* address)
-{
-    dmaStream->M1AR = reinterpret_cast<uint32_t>(address);
-}
-
-inline void DMAStream::clearStatusRegister() { *IFCR |= IFCR_MASK; }
-
-}  // namespace Boardcore
diff --git a/src/shared/drivers/dma/board_mappings/stm32f407xx_mappings.cpp b/src/shared/drivers/dma/board_mappings/stm32f407xx_mappings.cpp
new file mode 100644
index 000000000..613a53d21
--- /dev/null
+++ b/src/shared/drivers/dma/board_mappings/stm32f407xx_mappings.cpp
@@ -0,0 +1,232 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * 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 "../DMADefs.h"
+
+namespace Boardcore
+{
+
+namespace DMADefs
+{
+const std::multimap<Peripherals, std::pair<DMAStreamId, Channel>>
+    mapPeripherals = {
+
+        /**
+         * Here are defined the mappings between peripherals and
+         * streams.
+         *
+         * The problem is that some of these stream are used
+         * by miosix. The corresponding IRQHandlers are already defined
+         * in there, causing conflicts.
+         * For this reason the unavailable mappings are commented out.
+         */
+
+        // MEM-TO-MEM (only dma2 can perform mem-to-mem copy)
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str1, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        // {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str4, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str5, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str7, Channel::CHANNEL0}},
+
+        // SPI
+        {Peripherals::PE_SPI1_TX, {DMAStreamId::DMA2_Str5, Channel::CHANNEL3}},
+        // {Peripherals::PE_SPI1_TX, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL3}},
+        {Peripherals::PE_SPI1_RX, {DMAStreamId::DMA2_Str2, Channel::CHANNEL3}},
+        {Peripherals::PE_SPI1_RX, {DMAStreamId::DMA2_Str0, Channel::CHANNEL3}},
+
+        {Peripherals::PE_SPI2_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL0}},
+        // {Peripherals::PE_SPI2_RX, {DMAStreamId::DMA1_Str3,
+        // Channel::CHANNEL0}},
+
+        {Peripherals::PE_SPI3_TX, {DMAStreamId::DMA1_Str5, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL0}},
+
+        // UART & USART
+        {Peripherals::PE_USART1_TX,
+         {DMAStreamId::DMA2_Str7, Channel::CHANNEL4}},
+        {Peripherals::PE_USART1_RX,
+         {DMAStreamId::DMA2_Str2, Channel::CHANNEL4}},
+        {Peripherals::PE_USART1_RX,
+         {DMAStreamId::DMA2_Str5, Channel::CHANNEL4}},
+
+        {Peripherals::PE_USART2_TX,
+         {DMAStreamId::DMA1_Str6, Channel::CHANNEL4}},
+        {Peripherals::PE_USART2_RX,
+         {DMAStreamId::DMA1_Str5, Channel::CHANNEL4}},
+
+        // {Peripherals::PE_USART3_TX, {DMAStreamId::DMA1_Str3,
+        // Channel::CHANNEL4}},
+        {Peripherals::PE_USART3_TX,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL7}},
+        // {Peripherals::PE_USART3_RX, {DMAStreamId::DMA1_Str1,
+        // Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART4_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL4}},
+        {Peripherals::PE_UART4_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART5_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL4}},
+        {Peripherals::PE_UART5_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL4}},
+
+        {Peripherals::PE_USART6_TX,
+         {DMAStreamId::DMA2_Str6, Channel::CHANNEL5}},
+        {Peripherals::PE_USART6_TX,
+         {DMAStreamId::DMA2_Str7, Channel::CHANNEL5}},
+        {Peripherals::PE_USART6_RX,
+         {DMAStreamId::DMA2_Str1, Channel::CHANNEL5}},
+        {Peripherals::PE_USART6_RX,
+         {DMAStreamId::DMA2_Str2, Channel::CHANNEL5}},
+
+        // I2C
+        {Peripherals::PE_I2C1_TX, {DMAStreamId::DMA1_Str6, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_RX, {DMAStreamId::DMA1_Str5, Channel::CHANNEL1}},
+
+        {Peripherals::PE_I2C2_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL7}},
+        {Peripherals::PE_I2C2_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL7}},
+        // {Peripherals::PE_I2C2_RX, {DMAStreamId::DMA1_Str3,
+        // Channel::CHANNEL7}},
+
+        {Peripherals::PE_I2C3_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL3}},
+        {Peripherals::PE_I2C3_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL3}},
+
+        {Peripherals::PE_I2S2_EXT_TX,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL2}},
+        // {Peripherals::PE_I2S2_EXT_RX, {DMAStreamId::DMA1_Str3,
+        // Channel::CHANNEL3}},
+
+        {Peripherals::PE_I2S3_EXT_TX,
+         {DMAStreamId::DMA1_Str5, Channel::CHANNEL2}},
+        {Peripherals::PE_I2S3_EXT_RX,
+         {DMAStreamId::DMA1_Str2, Channel::CHANNEL2}},
+        {Peripherals::PE_I2S3_EXT_RX,
+         {DMAStreamId::DMA1_Str0, Channel::CHANNEL3}},
+
+        // TIMERS
+        {Peripherals::PE_TIM1_UP, {DMAStreamId::DMA2_Str5, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_TRIG,
+         {DMAStreamId::DMA2_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_TRIG,
+         {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_COM, {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str1, Channel::CHANNEL6}},
+        // {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH2, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH3, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH3, {DMAStreamId::DMA2_Str6, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH4, {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+
+        // {Peripherals::PE_TIM2_UP, {DMAStreamId::DMA1_Str1,
+        // Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_UP, {DMAStreamId::DMA1_Str7, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH1, {DMAStreamId::DMA1_Str5, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH2, {DMAStreamId::DMA1_Str6, Channel::CHANNEL3}},
+        // {Peripherals::PE_TIM2_CH3, {DMAStreamId::DMA1_Str1,
+        // Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH4, {DMAStreamId::DMA1_Str6, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH4, {DMAStreamId::DMA1_Str7, Channel::CHANNEL3}},
+
+        {Peripherals::PE_TIM3_UP, {DMAStreamId::DMA1_Str2, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_TRIG,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH1, {DMAStreamId::DMA1_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH2, {DMAStreamId::DMA1_Str5, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH3, {DMAStreamId::DMA1_Str7, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH4, {DMAStreamId::DMA1_Str2, Channel::CHANNEL5}},
+
+        {Peripherals::PE_TIM4_UP, {DMAStreamId::DMA1_Str6, Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH1, {DMAStreamId::DMA1_Str0, Channel::CHANNEL2}},
+        // {Peripherals::PE_TIM4_CH2, {DMAStreamId::DMA1_Str3,
+        // Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH3, {DMAStreamId::DMA1_Str7, Channel::CHANNEL2}},
+
+        {Peripherals::PE_TIM5_UP, {DMAStreamId::DMA1_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_UP, {DMAStreamId::DMA1_Str6, Channel::CHANNEL6}},
+        // {Peripherals::PE_TIM5_TRIG, {DMAStreamId::DMA1_Str1,
+        // Channel::CHANNEL6}},
+        // {Peripherals::PE_TIM5_TRIG, {DMAStreamId::DMA1_Str3,
+        // Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH1, {DMAStreamId::DMA1_Str2, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH2, {DMAStreamId::DMA1_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH3, {DMAStreamId::DMA1_Str0, Channel::CHANNEL6}},
+        // {Peripherals::PE_TIM5_CH4, {DMAStreamId::DMA1_Str1,
+        // Channel::CHANNEL6}},
+        // {Peripherals::PE_TIM5_CH4, {DMAStreamId::DMA1_Str3,
+        // Channel::CHANNEL6}},
+
+        // {Peripherals::PE_TIM6_UP, {DMAStreamId::DMA1_Str1,
+        // Channel::CHANNEL7}},
+
+        {Peripherals::PE_TIM7_UP, {DMAStreamId::DMA1_Str2, Channel::CHANNEL1}},
+        {Peripherals::PE_TIM7_UP, {DMAStreamId::DMA1_Str4, Channel::CHANNEL1}},
+
+        {Peripherals::PE_TIM8_UP, {DMAStreamId::DMA2_Str1, Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_TRIG,
+         {DMAStreamId::DMA2_Str7, Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_COM, {DMAStreamId::DMA2_Str7, Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH1, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM8_CH1, {DMAStreamId::DMA2_Str2, Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        // {Peripherals::PE_TIM8_CH2, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH3, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM8_CH3, {DMAStreamId::DMA2_Str4, Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH4, {DMAStreamId::DMA2_Str7, Channel::CHANNEL7}},
+
+        // Others
+        {Peripherals::PE_DAC1, {DMAStreamId::DMA1_Str5, Channel::CHANNEL7}},
+        {Peripherals::PE_DAC2, {DMAStreamId::DMA1_Str6, Channel::CHANNEL7}},
+
+        {Peripherals::PE_ADC1, {DMAStreamId::DMA2_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_ADC1, {DMAStreamId::DMA2_Str4, Channel::CHANNEL0}},
+
+        {Peripherals::PE_ADC2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL1}},
+        // {Peripherals::PE_ADC2, {DMAStreamId::DMA2_Str3, Channel::CHANNEL1}},
+
+        {Peripherals::PE_ADC3, {DMAStreamId::DMA2_Str0, Channel::CHANNEL2}},
+        {Peripherals::PE_ADC3, {DMAStreamId::DMA2_Str1, Channel::CHANNEL2}},
+
+        {Peripherals::PE_DCMI, {DMAStreamId::DMA2_Str1, Channel::CHANNEL1}},
+        {Peripherals::PE_DCMI, {DMAStreamId::DMA2_Str7, Channel::CHANNEL1}},
+
+        // {Peripherals::PE_SDIO, {DMAStreamId::DMA2_Str3, Channel::CHANNEL4}},
+        {Peripherals::PE_SDIO, {DMAStreamId::DMA2_Str6, Channel::CHANNEL4}},
+
+        {Peripherals::PE_CRYP_OUT, {DMAStreamId::DMA2_Str5, Channel::CHANNEL2}},
+        {Peripherals::PE_CRYP_IN, {DMAStreamId::DMA2_Str6, Channel::CHANNEL2}},
+
+        {Peripherals::PE_HASH_IN, {DMAStreamId::DMA2_Str7, Channel::CHANNEL2}},
+};
+
+}  // namespace DMADefs
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/dma/board_mappings/stm32f429xx_mappings.cpp b/src/shared/drivers/dma/board_mappings/stm32f429xx_mappings.cpp
new file mode 100644
index 000000000..9f3a667a9
--- /dev/null
+++ b/src/shared/drivers/dma/board_mappings/stm32f429xx_mappings.cpp
@@ -0,0 +1,262 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * 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 "../DMADefs.h"
+
+namespace Boardcore
+{
+
+namespace DMADefs
+{
+const std::multimap<Peripherals, std::pair<DMAStreamId, Channel>>
+    mapPeripherals = {
+
+        /**
+         * Here are defined the mappings between peripherals and
+         * streams.
+         *
+         * The problem is that some of these stream are used
+         * by miosix. The corresponding IRQHandlers are already defined
+         * in there, causing conflicts.
+         * For this reason the unavailable mappings are commented out.
+         */
+
+        // MEM-TO-MEM (only dma2 can perform mem-to-mem copy)
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str1, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        // {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str4, Channel::CHANNEL0}},
+        // {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        // {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL0}},
+
+        // SPI
+        // {Peripherals::PE_SPI1_TX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL3}}, {Peripherals::PE_SPI1_TX,
+        // {DMAStreamId::DMA2_Str3, Channel::CHANNEL3}},
+        {Peripherals::PE_SPI1_RX, {DMAStreamId::DMA2_Str2, Channel::CHANNEL3}},
+        {Peripherals::PE_SPI1_RX, {DMAStreamId::DMA2_Str0, Channel::CHANNEL3}},
+
+        {Peripherals::PE_SPI2_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI2_RX, {DMAStreamId::DMA1_Str3, Channel::CHANNEL0}},
+
+        {Peripherals::PE_SPI3_TX, {DMAStreamId::DMA1_Str5, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL0}},
+
+        {Peripherals::PE_SPI4_TX, {DMAStreamId::DMA2_Str1, Channel::CHANNEL4}},
+        {Peripherals::PE_SPI4_TX, {DMAStreamId::DMA2_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_SPI4_RX, {DMAStreamId::DMA2_Str0, Channel::CHANNEL4}},
+        // {Peripherals::PE_SPI4_RX, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL5}},
+
+        {Peripherals::PE_SPI5_TX, {DMAStreamId::DMA2_Str4, Channel::CHANNEL2}},
+        {Peripherals::PE_SPI5_TX, {DMAStreamId::DMA2_Str6, Channel::CHANNEL7}},
+        // {Peripherals::PE_SPI5_RX, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL2}},
+        // {Peripherals::PE_SPI5_RX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL7}},
+
+        // {Peripherals::PE_SPI6_TX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL1}},
+        {Peripherals::PE_SPI6_RX, {DMAStreamId::DMA2_Str6, Channel::CHANNEL1}},
+
+        // UART & USART
+        // {Peripherals::PE_USART1_TX,
+        //  {DMAStreamId::DMA2_Str7, Channel::CHANNEL4}},
+        {Peripherals::PE_USART1_RX,
+         {DMAStreamId::DMA2_Str2, Channel::CHANNEL4}},
+        // {Peripherals::PE_USART1_RX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL4}},
+
+        {Peripherals::PE_USART2_TX,
+         {DMAStreamId::DMA1_Str6, Channel::CHANNEL4}},
+        {Peripherals::PE_USART2_RX,
+         {DMAStreamId::DMA1_Str5, Channel::CHANNEL4}},
+
+        {Peripherals::PE_USART3_TX,
+         {DMAStreamId::DMA1_Str3, Channel::CHANNEL4}},
+        {Peripherals::PE_USART3_TX,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL7}},
+        {Peripherals::PE_USART3_RX,
+         {DMAStreamId::DMA1_Str1, Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART4_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL4}},
+        {Peripherals::PE_UART4_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART5_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL4}},
+        {Peripherals::PE_UART5_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART7_TX, {DMAStreamId::DMA1_Str1, Channel::CHANNEL5}},
+        {Peripherals::PE_UART7_RX, {DMAStreamId::DMA1_Str3, Channel::CHANNEL5}},
+
+        {Peripherals::PE_UART8_TX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL5}},
+        {Peripherals::PE_UART8_RX, {DMAStreamId::DMA1_Str6, Channel::CHANNEL5}},
+
+        {Peripherals::PE_USART6_TX,
+         {DMAStreamId::DMA2_Str6, Channel::CHANNEL5}},
+        // {Peripherals::PE_USART6_TX,
+        //  {DMAStreamId::DMA2_Str7, Channel::CHANNEL5}},
+        {Peripherals::PE_USART6_RX,
+         {DMAStreamId::DMA2_Str1, Channel::CHANNEL5}},
+        {Peripherals::PE_USART6_RX,
+         {DMAStreamId::DMA2_Str2, Channel::CHANNEL5}},
+
+        // I2C
+        {Peripherals::PE_I2C1_TX, {DMAStreamId::DMA1_Str6, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_RX, {DMAStreamId::DMA1_Str5, Channel::CHANNEL1}},
+
+        {Peripherals::PE_I2C2_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL7}},
+        {Peripherals::PE_I2C2_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL7}},
+        {Peripherals::PE_I2C2_RX, {DMAStreamId::DMA1_Str3, Channel::CHANNEL7}},
+
+        {Peripherals::PE_I2C3_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL3}},
+        {Peripherals::PE_I2C3_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL3}},
+
+        {Peripherals::PE_I2S2_EXT_TX,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL2}},
+        {Peripherals::PE_I2S2_EXT_RX,
+         {DMAStreamId::DMA1_Str3, Channel::CHANNEL3}},
+
+        {Peripherals::PE_I2S3_EXT_TX,
+         {DMAStreamId::DMA1_Str5, Channel::CHANNEL2}},
+        {Peripherals::PE_I2S3_EXT_RX,
+         {DMAStreamId::DMA1_Str2, Channel::CHANNEL2}},
+        {Peripherals::PE_I2S3_EXT_RX,
+         {DMAStreamId::DMA1_Str0, Channel::CHANNEL3}},
+
+        // TIMERS
+        // {Peripherals::PE_TIM1_UP, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_TRIG,
+         {DMAStreamId::DMA2_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_TRIG,
+         {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_COM, {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str1, Channel::CHANNEL6}},
+        // {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH2, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH3, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH3, {DMAStreamId::DMA2_Str6, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH4, {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+
+        {Peripherals::PE_TIM2_UP, {DMAStreamId::DMA1_Str1, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_UP, {DMAStreamId::DMA1_Str7, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH1, {DMAStreamId::DMA1_Str5, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH2, {DMAStreamId::DMA1_Str6, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH3, {DMAStreamId::DMA1_Str1, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH4, {DMAStreamId::DMA1_Str6, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH4, {DMAStreamId::DMA1_Str7, Channel::CHANNEL3}},
+
+        {Peripherals::PE_TIM3_UP, {DMAStreamId::DMA1_Str2, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_TRIG,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH1, {DMAStreamId::DMA1_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH2, {DMAStreamId::DMA1_Str5, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH3, {DMAStreamId::DMA1_Str7, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH4, {DMAStreamId::DMA1_Str2, Channel::CHANNEL5}},
+
+        {Peripherals::PE_TIM4_UP, {DMAStreamId::DMA1_Str6, Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH1, {DMAStreamId::DMA1_Str0, Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH2, {DMAStreamId::DMA1_Str3, Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH3, {DMAStreamId::DMA1_Str7, Channel::CHANNEL2}},
+
+        {Peripherals::PE_TIM5_UP, {DMAStreamId::DMA1_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_UP, {DMAStreamId::DMA1_Str6, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_TRIG,
+         {DMAStreamId::DMA1_Str1, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_TRIG,
+         {DMAStreamId::DMA1_Str3, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH1, {DMAStreamId::DMA1_Str2, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH2, {DMAStreamId::DMA1_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH3, {DMAStreamId::DMA1_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH4, {DMAStreamId::DMA1_Str1, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH4, {DMAStreamId::DMA1_Str3, Channel::CHANNEL6}},
+
+        {Peripherals::PE_TIM6_UP, {DMAStreamId::DMA1_Str1, Channel::CHANNEL7}},
+
+        {Peripherals::PE_TIM7_UP, {DMAStreamId::DMA1_Str2, Channel::CHANNEL1}},
+        {Peripherals::PE_TIM7_UP, {DMAStreamId::DMA1_Str4, Channel::CHANNEL1}},
+
+        {Peripherals::PE_TIM8_UP, {DMAStreamId::DMA2_Str1, Channel::CHANNEL7}},
+        // {Peripherals::PE_TIM8_TRIG,
+        //  {DMAStreamId::DMA2_Str7, Channel::CHANNEL7}},
+        // {Peripherals::PE_TIM8_COM, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH1, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM8_CH1, {DMAStreamId::DMA2_Str2, Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        // {Peripherals::PE_TIM8_CH2, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH3, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM8_CH3, {DMAStreamId::DMA2_Str4, Channel::CHANNEL7}},
+        // {Peripherals::PE_TIM8_CH4, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL7}},
+
+        // Others
+        {Peripherals::PE_DAC1, {DMAStreamId::DMA1_Str5, Channel::CHANNEL7}},
+        {Peripherals::PE_DAC2, {DMAStreamId::DMA1_Str6, Channel::CHANNEL7}},
+
+        {Peripherals::PE_ADC1, {DMAStreamId::DMA2_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_ADC1, {DMAStreamId::DMA2_Str4, Channel::CHANNEL0}},
+
+        {Peripherals::PE_ADC2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL1}},
+        // {Peripherals::PE_ADC2, {DMAStreamId::DMA2_Str3, Channel::CHANNEL1}},
+
+        {Peripherals::PE_ADC3, {DMAStreamId::DMA2_Str0, Channel::CHANNEL2}},
+        {Peripherals::PE_ADC3, {DMAStreamId::DMA2_Str1, Channel::CHANNEL2}},
+
+        {Peripherals::PE_SAI1_A, {DMAStreamId::DMA2_Str1, Channel::CHANNEL0}},
+        // {Peripherals::PE_SAI1_A, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL0}},
+
+        // {Peripherals::PE_SAI1_B, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_SAI1_B, {DMAStreamId::DMA2_Str4, Channel::CHANNEL1}},
+
+        {Peripherals::PE_DCMI, {DMAStreamId::DMA2_Str1, Channel::CHANNEL1}},
+        // {Peripherals::PE_DCMI, {DMAStreamId::DMA2_Str7, Channel::CHANNEL1}},
+
+        // {Peripherals::PE_SDIO, {DMAStreamId::DMA2_Str3, Channel::CHANNEL4}},
+        {Peripherals::PE_SDIO, {DMAStreamId::DMA2_Str6, Channel::CHANNEL4}},
+
+        // {Peripherals::PE_CRYP_OUT, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL2}},
+        {Peripherals::PE_CRYP_IN, {DMAStreamId::DMA2_Str6, Channel::CHANNEL2}},
+
+        // {Peripherals::PE_HASH_IN, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL2}},
+};
+
+}  // namespace DMADefs
+
+}  // namespace Boardcore
diff --git a/src/shared/drivers/dma/board_mappings/stm32f767xx_mappings.cpp b/src/shared/drivers/dma/board_mappings/stm32f767xx_mappings.cpp
new file mode 100644
index 000000000..e50f3d7ca
--- /dev/null
+++ b/src/shared/drivers/dma/board_mappings/stm32f767xx_mappings.cpp
@@ -0,0 +1,307 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Author: Fabrizio Monti
+ *
+ * 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 "../DMADefs.h"
+
+namespace Boardcore
+{
+
+namespace DMADefs
+{
+const std::multimap<Peripherals, std::pair<DMAStreamId, Channel>>
+    mapPeripherals = {
+
+        /**
+         * Here are defined the mappings between peripherals and
+         * streams.
+         *
+         * The problem is that some of these stream are used
+         * by miosix. The corresponding IRQHandlers are already defined
+         * in there, causing conflicts.
+         * For this reason the unavailable mappings are commented out.
+         */
+
+        // MEM-TO-MEM (only dma2 can perform mem-to-mem copy)
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str1, Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        // {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str4, Channel::CHANNEL0}},
+        // {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        // {Peripherals::PE_MEM_ONLY, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL0}},
+
+        // SPI
+        // {Peripherals::PE_SPI1_TX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL3}},
+        // {Peripherals::PE_SPI1_TX, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL3}},
+        {Peripherals::PE_SPI1_RX, {DMAStreamId::DMA2_Str2, Channel::CHANNEL3}},
+        {Peripherals::PE_SPI1_RX, {DMAStreamId::DMA2_Str0, Channel::CHANNEL3}},
+
+        {Peripherals::PE_SPI2_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI2_TX, {DMAStreamId::DMA1_Str6, Channel::CHANNEL9}},
+        {Peripherals::PE_SPI2_RX, {DMAStreamId::DMA1_Str1, Channel::CHANNEL9}},
+        {Peripherals::PE_SPI2_RX, {DMAStreamId::DMA1_Str3, Channel::CHANNEL0}},
+
+        {Peripherals::PE_SPI3_TX, {DMAStreamId::DMA1_Str5, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_SPI3_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL0}},
+
+        {Peripherals::PE_SPI4_TX, {DMAStreamId::DMA2_Str1, Channel::CHANNEL4}},
+        {Peripherals::PE_SPI4_TX, {DMAStreamId::DMA2_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_SPI4_TX, {DMAStreamId::DMA2_Str2, Channel::CHANNEL9}},
+        {Peripherals::PE_SPI4_RX, {DMAStreamId::DMA2_Str0, Channel::CHANNEL4}},
+        // {Peripherals::PE_SPI4_RX, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL5}},
+
+        {Peripherals::PE_SPI5_TX, {DMAStreamId::DMA2_Str4, Channel::CHANNEL2}},
+        {Peripherals::PE_SPI5_TX, {DMAStreamId::DMA2_Str6, Channel::CHANNEL7}},
+        // {Peripherals::PE_SPI5_RX, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL2}},
+        // {Peripherals::PE_SPI5_RX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL7}},
+        // {Peripherals::PE_SPI5_RX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL9}},
+
+        // {Peripherals::PE_SPI6_TX, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL1}},
+        {Peripherals::PE_SPI6_RX, {DMAStreamId::DMA2_Str6, Channel::CHANNEL1}},
+
+        // {Peripherals::PE_QUADSPI, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL3}},
+        {Peripherals::PE_QUADSPI, {DMAStreamId::DMA2_Str2, Channel::CHANNEL11}},
+
+        // UART & USART
+        // {Peripherals::PE_USART1_TX,
+        //  {DMAStreamId::DMA2_Str7, Channel::CHANNEL4}},
+        {Peripherals::PE_USART1_RX,
+         {DMAStreamId::DMA2_Str2, Channel::CHANNEL4}},
+        // {Peripherals::PE_USART1_RX,
+        //  {DMAStreamId::DMA2_Str5, Channel::CHANNEL4}},
+
+        {Peripherals::PE_USART2_TX,
+         {DMAStreamId::DMA1_Str6, Channel::CHANNEL4}},
+        {Peripherals::PE_USART2_RX,
+         {DMAStreamId::DMA1_Str5, Channel::CHANNEL4}},
+
+        {Peripherals::PE_USART3_TX,
+         {DMAStreamId::DMA1_Str3, Channel::CHANNEL4}},
+        {Peripherals::PE_USART3_TX,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL7}},
+        {Peripherals::PE_USART3_RX,
+         {DMAStreamId::DMA1_Str1, Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART4_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL4}},
+        {Peripherals::PE_UART4_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART5_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL4}},
+        {Peripherals::PE_UART5_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL4}},
+
+        {Peripherals::PE_UART7_TX, {DMAStreamId::DMA1_Str1, Channel::CHANNEL5}},
+        {Peripherals::PE_UART7_RX, {DMAStreamId::DMA1_Str3, Channel::CHANNEL5}},
+
+        {Peripherals::PE_UART8_TX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL5}},
+        {Peripherals::PE_UART8_RX, {DMAStreamId::DMA1_Str6, Channel::CHANNEL5}},
+
+        {Peripherals::PE_USART6_TX,
+         {DMAStreamId::DMA2_Str6, Channel::CHANNEL5}},
+        // {Peripherals::PE_USART6_TX,
+        //  {DMAStreamId::DMA2_Str7, Channel::CHANNEL5}},
+        {Peripherals::PE_USART6_RX,
+         {DMAStreamId::DMA2_Str1, Channel::CHANNEL5}},
+        {Peripherals::PE_USART6_RX,
+         {DMAStreamId::DMA2_Str2, Channel::CHANNEL5}},
+
+        // I2C
+        {Peripherals::PE_I2C1_TX, {DMAStreamId::DMA1_Str6, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_RX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL1}},
+        {Peripherals::PE_I2C1_RX, {DMAStreamId::DMA1_Str5, Channel::CHANNEL1}},
+
+        {Peripherals::PE_I2C2_TX, {DMAStreamId::DMA1_Str7, Channel::CHANNEL7}},
+        {Peripherals::PE_I2C2_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL8}},
+        {Peripherals::PE_I2C2_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL7}},
+        {Peripherals::PE_I2C2_RX, {DMAStreamId::DMA1_Str3, Channel::CHANNEL7}},
+
+        {Peripherals::PE_I2C3_TX, {DMAStreamId::DMA1_Str4, Channel::CHANNEL3}},
+        {Peripherals::PE_I2C3_TX, {DMAStreamId::DMA1_Str0, Channel::CHANNEL8}},
+        {Peripherals::PE_I2C3_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL3}},
+        {Peripherals::PE_I2C3_RX, {DMAStreamId::DMA1_Str1, Channel::CHANNEL1}},
+
+        {Peripherals::PE_I2C4_TX, {DMAStreamId::DMA1_Str6, Channel::CHANNEL8}},
+        {Peripherals::PE_I2C4_RX, {DMAStreamId::DMA1_Str2, Channel::CHANNEL2}},
+        {Peripherals::PE_I2C4_RX, {DMAStreamId::DMA1_Str1, Channel::CHANNEL8}},
+
+        // TIMERS
+        // {Peripherals::PE_TIM1_UP, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_TRIG,
+         {DMAStreamId::DMA2_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_TRIG,
+         {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_COM, {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str1, Channel::CHANNEL6}},
+        // {Peripherals::PE_TIM1_CH1, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH2, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH3, {DMAStreamId::DMA2_Str6, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM1_CH3, {DMAStreamId::DMA2_Str6, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM1_CH4, {DMAStreamId::DMA2_Str4, Channel::CHANNEL6}},
+
+        {Peripherals::PE_TIM2_UP, {DMAStreamId::DMA1_Str1, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_UP, {DMAStreamId::DMA1_Str7, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH1, {DMAStreamId::DMA1_Str5, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH2, {DMAStreamId::DMA1_Str6, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH3, {DMAStreamId::DMA1_Str1, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH4, {DMAStreamId::DMA1_Str6, Channel::CHANNEL3}},
+        {Peripherals::PE_TIM2_CH4, {DMAStreamId::DMA1_Str7, Channel::CHANNEL3}},
+
+        {Peripherals::PE_TIM3_UP, {DMAStreamId::DMA1_Str2, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_TRIG,
+         {DMAStreamId::DMA1_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH1, {DMAStreamId::DMA1_Str4, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH2, {DMAStreamId::DMA1_Str5, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH3, {DMAStreamId::DMA1_Str7, Channel::CHANNEL5}},
+        {Peripherals::PE_TIM3_CH4, {DMAStreamId::DMA1_Str2, Channel::CHANNEL5}},
+
+        {Peripherals::PE_TIM4_UP, {DMAStreamId::DMA1_Str6, Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH1, {DMAStreamId::DMA1_Str0, Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH2, {DMAStreamId::DMA1_Str3, Channel::CHANNEL2}},
+        {Peripherals::PE_TIM4_CH3, {DMAStreamId::DMA1_Str7, Channel::CHANNEL2}},
+
+        {Peripherals::PE_TIM5_UP, {DMAStreamId::DMA1_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_UP, {DMAStreamId::DMA1_Str6, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_TRIG,
+         {DMAStreamId::DMA1_Str1, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_TRIG,
+         {DMAStreamId::DMA1_Str3, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH1, {DMAStreamId::DMA1_Str2, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH2, {DMAStreamId::DMA1_Str4, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH3, {DMAStreamId::DMA1_Str0, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH4, {DMAStreamId::DMA1_Str1, Channel::CHANNEL6}},
+        {Peripherals::PE_TIM5_CH4, {DMAStreamId::DMA1_Str3, Channel::CHANNEL6}},
+
+        {Peripherals::PE_TIM6_UP, {DMAStreamId::DMA1_Str1, Channel::CHANNEL7}},
+
+        {Peripherals::PE_TIM7_UP, {DMAStreamId::DMA1_Str2, Channel::CHANNEL1}},
+        {Peripherals::PE_TIM7_UP, {DMAStreamId::DMA1_Str4, Channel::CHANNEL1}},
+
+        {Peripherals::PE_TIM8_UP, {DMAStreamId::DMA2_Str1, Channel::CHANNEL7}},
+        // {Peripherals::PE_TIM8_TRIG,
+        //  {DMAStreamId::DMA2_Str7, Channel::CHANNEL7}},
+        // {Peripherals::PE_TIM8_COM, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH1, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM8_CH1, {DMAStreamId::DMA2_Str2, Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        // {Peripherals::PE_TIM8_CH2, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL7}},
+        {Peripherals::PE_TIM8_CH3, {DMAStreamId::DMA2_Str2, Channel::CHANNEL0}},
+        {Peripherals::PE_TIM8_CH3, {DMAStreamId::DMA2_Str4, Channel::CHANNEL7}},
+        // {Peripherals::PE_TIM8_CH4, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL7}},
+
+        // Others
+        {Peripherals::PE_DAC1, {DMAStreamId::DMA1_Str5, Channel::CHANNEL7}},
+        {Peripherals::PE_DAC2, {DMAStreamId::DMA1_Str6, Channel::CHANNEL7}},
+
+        {Peripherals::PE_ADC1, {DMAStreamId::DMA2_Str0, Channel::CHANNEL0}},
+        {Peripherals::PE_ADC1, {DMAStreamId::DMA2_Str4, Channel::CHANNEL0}},
+
+        {Peripherals::PE_ADC2, {DMAStreamId::DMA2_Str2, Channel::CHANNEL1}},
+        // {Peripherals::PE_ADC2, {DMAStreamId::DMA2_Str3, Channel::CHANNEL1}},
+
+        {Peripherals::PE_ADC3, {DMAStreamId::DMA2_Str0, Channel::CHANNEL2}},
+        {Peripherals::PE_ADC3, {DMAStreamId::DMA2_Str1, Channel::CHANNEL2}},
+
+        {Peripherals::PE_SAI1_A, {DMAStreamId::DMA2_Str1, Channel::CHANNEL0}},
+        {Peripherals::PE_SAI1_A, {DMAStreamId::DMA2_Str6, Channel::CHANNEL10}},
+        // {Peripherals::PE_SAI1_A, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_SAI2_A, {DMAStreamId::DMA2_Str4, Channel::CHANNEL3}},
+        {Peripherals::PE_SAI2_A, {DMAStreamId::DMA2_Str2, Channel::CHANNEL10}},
+
+        // {Peripherals::PE_SAI1_B, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_SAI1_B, {DMAStreamId::DMA2_Str4, Channel::CHANNEL1}},
+        {Peripherals::PE_SAI1_B, {DMAStreamId::DMA2_Str0, Channel::CHANNEL10}},
+        // {Peripherals::PE_SAI2_B, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL0}},
+        {Peripherals::PE_SAI2_B, {DMAStreamId::DMA2_Str6, Channel::CHANNEL3}},
+        {Peripherals::PE_SAI2_B, {DMAStreamId::DMA2_Str1, Channel::CHANNEL10}},
+
+        {Peripherals::PE_DCMI, {DMAStreamId::DMA2_Str1, Channel::CHANNEL1}},
+        // {Peripherals::PE_DCMI, {DMAStreamId::DMA2_Str7, Channel::CHANNEL1}},
+
+        // {Peripherals::PE_SDMMC1, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL4}},
+        {Peripherals::PE_SDMMC1, {DMAStreamId::DMA2_Str6, Channel::CHANNEL4}},
+        {Peripherals::PE_SDMMC2, {DMAStreamId::DMA2_Str0, Channel::CHANNEL11}},
+        // {Peripherals::PE_SDMMC2, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL11}},
+
+        // {Peripherals::PE_CRYP_OUT, {DMAStreamId::DMA2_Str5,
+        // Channel::CHANNEL2}},
+        {Peripherals::PE_CRYP_IN, {DMAStreamId::DMA2_Str6, Channel::CHANNEL2}},
+
+        // {Peripherals::PE_HASH_IN, {DMAStreamId::DMA2_Str7,
+        // Channel::CHANNEL2}},
+
+        {Peripherals::PE_SPDIFRX_DT,
+         {DMAStreamId::DMA1_Str1, Channel::CHANNEL0}},
+        {Peripherals::PE_SPDIFRX_CS,
+         {DMAStreamId::DMA1_Str6, Channel::CHANNEL0}},
+
+        {Peripherals::PE_DFSDM1_FLT0,
+         {DMAStreamId::DMA2_Str0, Channel::CHANNEL8}},
+        {Peripherals::PE_DFSDM1_FLT0,
+         {DMAStreamId::DMA2_Str4, Channel::CHANNEL8}},
+        {Peripherals::PE_DFSDM1_FLT1,
+         {DMAStreamId::DMA2_Str1, Channel::CHANNEL8}},
+        // {Peripherals::PE_DFSDM1_FLT1,
+        //  {DMAStreamId::DMA2_Str5, Channel::CHANNEL8}},
+        {Peripherals::PE_DFSDM1_FLT2,
+         {DMAStreamId::DMA2_Str2, Channel::CHANNEL8}},
+        {Peripherals::PE_DFSDM1_FLT2,
+         {DMAStreamId::DMA2_Str6, Channel::CHANNEL8}},
+        // {Peripherals::PE_DFSDM1_FLT3, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL8}},
+        // {Peripherals::PE_DFSDM1_FLT3,
+        //  {DMAStreamId::DMA2_Str7, Channel::CHANNEL8}},
+
+        {Peripherals::PE_JPEG_IN, {DMAStreamId::DMA2_Str0, Channel::CHANNEL9}},
+        // {Peripherals::PE_JPEG_IN, {DMAStreamId::DMA2_Str3,
+        // Channel::CHANNEL9}}
+        {Peripherals::PE_JPEG_OUT, {DMAStreamId::DMA2_Str1, Channel::CHANNEL9}},
+        {Peripherals::PE_JPEG_OUT, {DMAStreamId::DMA2_Str4, Channel::CHANNEL9}},
+};
+
+}  // namespace DMADefs
+
+}  // namespace Boardcore
diff --git a/src/tests/drivers/test-dma-mem-to-mem.cpp b/src/tests/drivers/test-dma-mem-to-mem.cpp
new file mode 100644
index 000000000..6eef0b9f2
--- /dev/null
+++ b/src/tests/drivers/test-dma-mem-to-mem.cpp
@@ -0,0 +1,87 @@
+/* Copyright (c) 2025 Skyward Experimental Rocketry
+ * Authors: Alberto Nidasio, Fabrizio Monti
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include <drivers/dma/DMA.h>
+#include <miosix.h>
+#include <util/util.h>
+
+using namespace miosix;
+using namespace Boardcore;
+
+void printBuffer(uint8_t* buffer, size_t size);
+
+int main()
+{
+    DMAStreamGuard stream = DMADriver::instance().acquireStreamForPeripheral(
+        DMADefs::Peripherals::PE_MEM_ONLY);
+
+    if (!stream.isValid())
+    {
+        printf("Error, cannot allocate dma stream\n");
+        return 0;
+    }
+
+    /**
+     * In this test we want to copy a buffer1 into buffer2 with the DMA.
+     */
+
+    uint8_t buffer1[8] = {1, 2, 3, 4, 5, 6, 7, 8};
+    uint8_t buffer2[8] = {0};
+
+    printf("Before:\n");
+    printf("Buffer 1:\n");
+    printBuffer(buffer1, sizeof(buffer1));
+    printf("Buffer 2:\n");
+    printBuffer(buffer2, sizeof(buffer2));
+
+    DMATransaction trn{
+        .direction         = DMATransaction::Direction::MEM_TO_MEM,
+        .srcSize           = DMATransaction::DataSize::BITS_8,
+        .dstSize           = DMATransaction::DataSize::BITS_8,
+        .srcAddress        = buffer1,
+        .dstAddress        = buffer2,
+        .numberOfDataItems = sizeof(buffer1),
+        .srcIncrement      = true,
+        .dstIncrement      = true,
+        .enableTransferCompleteInterrupt = true,
+    };
+    stream->setup(trn);
+    stream->enable();
+
+    stream->waitForTransferComplete();
+
+    printf("After:\n");
+    printf("Buffer 1:\n");
+    printBuffer(buffer1, sizeof(buffer1));
+    printf("Buffer 2:\n");
+    printBuffer(buffer2, sizeof(buffer2));
+
+    return 0;
+}
+
+void printBuffer(uint8_t* buffer, size_t size)
+{
+    for (size_t i = 0; i < size - 1; i++)
+        printf("%x,", buffer[i]);
+
+    printf("%x\n", buffer[size - 1]);
+}
-- 
GitLab