diff --git a/src/modm/platform/dma/stm32/f4/dma.cpp.in b/src/modm/platform/dma/stm32/f4/dma.cpp.in new file mode 100644 index 0000000000..f9e6fea7ca --- /dev/null +++ b/src/modm/platform/dma/stm32/f4/dma.cpp.in @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2020, Matthew Arnold + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "dma.hpp" + +%% for streams in dma["streams"] + %% for stream in streams.stream +MODM_ISR(DMA{{ streams.instance }}_Stream{{ stream.position}}) +{ + using namespace modm::platform; + Dma{{ streams.instance }}::Stream::interruptHandler(); +} + + %% endfor +%% endfor + diff --git a/src/modm/platform/dma/stm32/f4/dma.hpp.in b/src/modm/platform/dma/stm32/f4/dma.hpp.in new file mode 100644 index 0000000000..def252c7d7 --- /dev/null +++ b/src/modm/platform/dma/stm32/f4/dma.hpp.in @@ -0,0 +1,349 @@ +/* + * Copyright (c) 2014, Kevin Läufer + * Copyright (c) 2014-2017, Niklas Hauser + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2020, Matthew Arnold + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_DMA_HPP +#define MODM_STM32_DMA_HPP + +#include + +#include + +#include "dma_hal.hpp" + +namespace modm +{ +namespace platform +{ +/** + * DMA controller for stm32f4 series microcontrollers + */ +template +class DmaController : public DmaBase +{ + static_assert(ID >= 1 && ID <= {{ dma.instance | length }}, "dma id invalid"); + +public: + /** + * Enable the DMA controller in the RCC. + */ + static void enableRcc() + { +%% for controller in dmaController + %% if controller.dma_controller == 1 + if constexpr (ID == 1) + { + Rcc::enable(); + } + %% else + else if constexpr (ID == {{ controller.dma_controller }}) + { + Rcc::enable(); + } + %% endif +%% endfor + } + + /** + * Disable the DMA controller in the RCC. + */ + static void disableRcc() + { +%% for controller in dmaController + %% if controller.dma_controller == 1 + if constexpr (ID == 1) + { + Rcc::disable(); + } + %% else + else if constexpr (ID == {{ controller.dma_controller }}) + { + Rcc::disable(); + } + %% endif +%% endfor + } + + /** + * Class representing a DMA channel/stream. + */ + template + class Stream + { + using ControlHal = DmaHal; + using StreamHal = DmaStreamHal; + + public: + static constexpr uint32_t DMA_ID = ID; + static constexpr DmaBase::StreamID STREAM_ID = SID; + + /** + * Configure the DMA channel + * + * @see DmaStreamHal::configure + */ + static void configure( + ChannelSelection channel, + DataTransferDirection direction, + PeripheralIncrementMode periphInc, + MemoryIncrementMode memInc, + PeripheralDataSize peripDataSize, + MemoryDataSize memDataSize, + ControlMode mode, + PriorityLevel priority, + FifoMode fifoMode, + FifoThreshold fifoThreshold, + MemoryBurstTransfer memBurst, + PeripheralBurstTransfer periphBurst) + { + StreamHal::configure( + channel, + direction, + periphInc, + memInc, + peripDataSize, + memDataSize, + mode, + priority, + fifoMode, + fifoThreshold, + memBurst, + periphBurst); + } + + static void selectChannel(DmaBase::ChannelSelection channel) + { + StreamHal::selectChannel(channel); + } + + /** + * Start the transfer of the DMA channel + */ + static void enable() + { + ControlHal::clearInterruptFlags(STREAM_ID); + StreamHal::enable(); + } + /** + * Stop a DMA channel transfer + */ + static bool disable() { return StreamHal::disable(); } + + /** + * Get the direction of the data transfer + */ + static DataTransferDirection getDataTransferDirection() + { + return StreamHal::getDataTransferDirection(); + } + + /** + * Configure the DMA channel to write to the requested data buffer length bytes. + * + * @note When in continuous read mode, other read operations are disabled. + */ + static bool configureRead(uint8_t *data, std::size_t length) + { + // Disable to allow modifications of the DMA stream control register. + if (!disable()) + { + return false; + } + // configure total # of bytes + setDataLength(length); + // Write the memory address in the DMA control register to configure it as the + // destination of the transfer. The data will be loaded from USART_DR to this memory + // area after each RXNE event. + setDestinationAddress(reinterpret_cast(data)); + // activate channel in DMA control register + enable(); + return true; + } + + static bool configureWrite(uint8_t *data, std::size_t length) + { + if (disable()) + { + return false; + } + setDataLength(length); + setSourceAddress(reinterpret_cast(data)); + enable(); + return true; + } + + /** + * Set the memory address of the DMA channel + * + * @note In Mem2Mem mode use this method to set the memory source address. + * + * @param[in] address Source address + */ + static void setSourceAddress(uintptr_t address) { StreamHal::setSourceAddress(address); } + + /** + * Set the peripheral address of the DMA channel + * + * @note In Mem2Mem mode use this method to set the memory destination address. + * + * @param[in] address Destination address + */ + static void setDestinationAddress(uintptr_t address) + { + StreamHal::setDestinationAddress(address); + } + + /** + * Set the length of data to be transfered + */ + static void setDataLength(std::size_t length) { StreamHal::setDataLength(length); } + + /** + * Set the IRQ handler for transfer errors. + * + * The handler will be called from the channels IRQ handler function + * when the IRQ status indicates an error occured. + */ + static void setTransferErrorIrqHandler(IrqHandler irqHandler) + { + transferError = irqHandler; + } + + /** + * Set the IRQ handler for transfer complete. + * + * Called by the channels IRQ handler when the transfer is complete. + */ + static void setTransferCompleteIrqHandler(IrqHandler irqHandler) + { + transferComplete = irqHandler; + } + + static uint32_t getInterruptFlags() { return ControlHal::getInterruptFlags(STREAM_ID); } + + /** + * IRQ handler of the DMA channel. + * + * Reads the IRQ status and checks for error or transfer complete. In case + * of error the DMA channel will be disabled. + */ + static void interruptHandler() + { + uint32_t currIsrs = ControlHal::getInterruptFlags(STREAM_ID); + ControlHal::clearInterruptFlags(STREAM_ID); + if (currIsrs & (uint32_t(InterruptStatusMsks::TRANSFER_ERROR) | + uint32_t(InterruptStatusMsks::DIRECT_MODE_ERROR) | + uint32_t(InterruptStatusMsks::FIFO_ERROR))) + { + if (transferError != nullptr) + { + transferError(); + } + } + if ((currIsrs & uint32_t(InterruptStatusMsks::TRANSFER_COMPLETE)) && transferComplete) + { + transferComplete(); + } + } + + /** + * Enable the IRQ vector of the channel. + * + * @param[in] priority Priority of the IRQ + */ + static void enableInterruptVector(uint32_t priority = 1) + { + NVIC_SetPriority(DmaBase::Nvic::DmaIrqs[uint32_t(STREAM_ID)], priority); + NVIC_EnableIRQ(DmaBase::Nvic::DmaIrqs[uint32_t(STREAM_ID)]); + } + + /** + * Disable the IRQ vector of the channel. + */ + static void disableInterruptVector() + { + NVIC_DisableIRQ(DmaBase::Nvic::DmaIrqs[uint32_t(STREAM_ID)]); + } + + /** + * Enable the specified interrupt of the channel. + */ + static void enableInterrupt(Interrupt_t irq) { StreamHal::enableInterrupt(irq); } + + /** + * Disable the specified interrupt of the channel. + */ + static void disableInterrupt(Interrupt_t irq) { StreamHal::disableInterrupt(irq); } + + template + struct RequestMapping { + }; + private: + static inline DmaBase::IrqHandler transferError{nullptr}; + static inline DmaBase::IrqHandler transferComplete{nullptr}; + }; +}; // class DmaController + +/* + * Derive DMA controller classes for convenience. Every derived class defines + * the streams available on that controller. + */ +%% for streams in dma["streams"] +class Dma{{ streams.instance }} : public DmaController<{{ streams.instance }}> +{ +public: + %% for stream in streams.stream + using Stream{{ stream.position }} = DmaController<{{ streams.instance }}>::Stream; + %% endfor +}; // class Dma1 + +%% endfor + +/* + * Specialization of the RequestMapping. For all hardware supported by DMA the + * RequestMapping structure defines the channel and the Request. It can be used + * by hardware classes to verify that the provided channel is valid and to + * get the value to set in setPeripheralRequest(). + * + * Example: + * template + * class SpiMaster1_Dma : public SpiMaster1 + * { + * using RxChannel = typename DmaRx::template RequestMapping::Channel; + * using TxChannel = typename DmaTx::template RequestMapping::Channel; + * static constexpr DmaBase::Request RxRequest = DmaRx::template RequestMapping::Request; + * static constexpr DmaBase::Request TxRequest = DmaTx::template RequestMapping::Request; + * + * ... + * }; + */ +%% for controller in dmaController + %% for streamChMapping in controller.stream_ch_mapping + %% for driver in streamChMapping.drivers +template<> +template<> +template<> +struct DmaController<{{ controller.dma_controller }}>::Stream::RequestMapping +{ + using Stream = DmaController<{{ controller.dma_controller }}>::Stream; + static constexpr DmaBase::ChannelSelection Channel = DmaBase::ChannelSelection::CHANNEL_{{ streamChMapping.channel }}; +}; + + %% endfor + %% endfor +%% endfor +} // namespace platform +} // namespace modm + +#endif // MODM_STM32_DMA_HPP + diff --git a/src/modm/platform/dma/stm32/f4/dma_base.hpp.in b/src/modm/platform/dma/stm32/f4/dma_base.hpp.in new file mode 100644 index 0000000000..b092289597 --- /dev/null +++ b/src/modm/platform/dma/stm32/f4/dma_base.hpp.in @@ -0,0 +1,287 @@ +/* + * Copyright (c) 2014, Kevin Läufer + * Copyright (c) 2014-2017, Niklas Hauser + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2020, Matthew Arnold + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_DMA_BASE_HPP +#define MODM_STM32_DMA_BASE_HPP + +#include +#include "../device.hpp" + +#include +#include +#include + + +namespace modm +{ +namespace platform +{ +/** + * Class that defines register masks and interrupt handlers for DMA. + * + * @author Matthew Arnold + */ +class DmaBase +{ +public: + // Enums, for register bit masking + + // Use the following bit masks on a DMA stream x configuration register + // DMA_xCR, where x = [0,7] + + // Bits 27:25 + enum class ChannelSelection : uint32_t + { + CHANNEL_0 = 0, + CHANNEL_1 = DMA_SxCR_CHSEL_0, + CHANNEL_2 = DMA_SxCR_CHSEL_1, + CHANNEL_3 = DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0, + CHANNEL_4 = DMA_SxCR_CHSEL_2, + CHANNEL_5 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_0, + CHANNEL_6 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1, + CHANNEL_7 = DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0, + }; + + // Bits 24:23 + enum class MemoryBurstTransfer : uint32_t + { + SINGLE = 0, + INCREMENT_4 = DMA_SxCR_MBURST_0, + INCREMENT_8 = DMA_SxCR_MBURST_1, + INCREMENT_16 = DMA_SxCR_MBURST, + }; + + // Bits 22:21 + enum class PeripheralBurstTransfer : uint32_t + { + SINGLE = 0, + INCREMENT_4 = DMA_SxCR_PBURST_0, + INCREMENT_8 = DMA_SxCR_PBURST_1, + INCREMENT_16 = DMA_SxCR_PBURST, + }; + + // Bit 19 + // Only used in double buffer mode + enum class CurrentTarget : uint32_t + { + MEMORY_0 = 0, + MEMORY_1 = DMA_SxCR_CT, + }; + + // Bit 18 + enum class DoubleBufferMode : uint32_t + { + NO_BUFFER_SWITCHING = 0, + MEMORY_TARGET_SWITCHED = DMA_SxCR_DBM, + }; + + // Bits 17:16 + enum class PriorityLevel : uint32_t + { + LOW = 0, + MEDIUM = DMA_SxCR_PL_0, + HIGH = DMA_SxCR_PL_1, + VERY_HIGH = DMA_SxCR_PL_0 | DMA_SxCR_PL_1, + }; + + // Bit 15 + enum class PeripheralIncrementOffsetSize : uint32_t + { + LINKED_TO_PSIZE = 0, + FIXED_TO_4 = DMA_SxCR_PINC, + }; + + // Bits 14:13 + /// In direct mode (if the FIFO is not used) + /// MSIZE is forced by hardware to the same value as PSIZE + enum class MemoryDataSize : uint32_t + { + BYTE = 0, + HALF_WORD = DMA_SxCR_MSIZE_0, + WORD = DMA_SxCR_MSIZE_1, + }; + + // Bits 12:11 + enum class PeripheralDataSize : uint32_t + { + BYTE = 0, + HALF_WORD = DMA_SxCR_PSIZE_0, + WORD = DMA_SxCR_PSIZE_1, + }; + + // Bit 10 + enum class MemoryIncrementMode : uint32_t + { + FIXED = 0, + INCREMENT = DMA_SxCR_MINC, ///< incremented according to MemoryDataSize + }; + + // Bit 9 + enum class PeripheralIncrementMode : uint32_t + { + FIXED = 0, + INCREMENT = DMA_SxCR_PINC, ///< incremented according to PeripheralDataSize + }; + + // Bits 8 and 5 + enum class ControlMode : uint32_t + { + NORMAL = 0, + CIRCULAR = DMA_SxCR_CIRC, + PERIPHERAL_FLOW = DMA_SxCR_PFCTRL, + }; + + // Bits 7:6 + enum class DataTransferDirection : uint32_t + { + ///< Source: DMA_SxPAR; Sink: DMA_SxM0AR + PERIPH_TO_MEM = 0, + ///< Source: DMA_SxM0AR; Sink: DMA_SxPAR + MEM_TO_PERIPH = DMA_SxCR_DIR_0, + ///< Source: DMA_SxPAR; Sink: DMA_SxM0AR + MEM_TO_MEM = DMA_SxCR_DIR_1, + }; + + // Bit 1-4 are interrupt enable settings. + enum class Interrupt + { + TRANSFER_COMPLETE = DMA_SxCR_TCIE, + HALF_TRANSFER_COMPLETE = DMA_SxCR_HTIE, + ERROR = DMA_SxCR_TEIE, + ALL = DMA_SxCR_TCIE | DMA_SxCR_HTIE | DMA_SxCR_DMEIE, + }; + MODM_FLAGS32(Interrupt); + + /** + * Bit 0 + * + * When disabled, software allowed to program the Configuration and FIFO + * bit registers. It is forbidden to write these registers when EN bit is + * read as 1. + * @note 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. + */ + enum class StreamEnableFlag : uint32_t + { + STREAM_DISABLED = 0, + STREAM_ENABLED = DMA_SxCR_EN, + }; + + // End DMA_xCR masks + + // masks for the DMA_SxFCR register (fifo control) + + enum class FifoMode : uint32_t + { + DISABLED = 0, + ENABLED = DMA_SxFCR_DMDIS, + }; + + enum class FifoThreshold : uint32_t + { + QUARTER_FULL = 0, + HALFFULL = DMA_SxFCR_FTH_0, + THREE_QUARTER_FULL = DMA_SxFCR_FTH_1, + FULL = DMA_SxFCR_FTH, + }; + + // End DMA_SxFCR masks + + %% set stream_count = namespace(max_streams = 0) +%% for controller in dmaController + %% for stream in controller.stream_ch_mapping + %% if stream_count.max_streams < stream.stream + %% set stream_count.max_streams = stream.stream + %% endif + %% endfor +%% endfor + enum class StreamID : uint32_t + { +%% for stream in range(0, stream_count.max_streams + 1) + STREAM_{{ stream }} = {{ stream }}, +%% endfor + }; + + enum class Signal : uint8_t { +%% for signal in dmaSignals + {{ signal }}, +%% endfor + }; + +protected: + /** + * Use to clear interrupts on a particular stream. Masks all interrupts for the + * specified stream. + */ + static constexpr uint32_t dmaStreamIFCRMasks[] { + DMA_LIFCR_CFEIF1 | DMA_LIFCR_CDMEIF1 | DMA_LIFCR_CTEIF1 | DMA_LIFCR_CHTIF1 | DMA_LIFCR_CTCIF1, + DMA_LIFCR_CFEIF2 | DMA_LIFCR_CDMEIF2 | DMA_LIFCR_CTEIF2 | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTCIF2, + DMA_LIFCR_CFEIF3 | DMA_LIFCR_CDMEIF3 | DMA_LIFCR_CTEIF3 | DMA_LIFCR_CHTIF3 | DMA_LIFCR_CTCIF3, + DMA_HIFCR_CFEIF4 | DMA_HIFCR_CDMEIF4 | DMA_HIFCR_CTEIF4 | DMA_HIFCR_CHTIF4 | DMA_HIFCR_CTCIF4, + DMA_HIFCR_CFEIF5 | DMA_HIFCR_CDMEIF5 | DMA_HIFCR_CTEIF5 | DMA_HIFCR_CHTIF5 | DMA_HIFCR_CTCIF5, + DMA_HIFCR_CFEIF6 | DMA_HIFCR_CDMEIF6 | DMA_HIFCR_CTEIF6 | DMA_HIFCR_CHTIF6 | DMA_HIFCR_CTCIF6, + DMA_HIFCR_CFEIF7 | DMA_HIFCR_CDMEIF7 | DMA_HIFCR_CTEIF7 | DMA_HIFCR_CHTIF7 | DMA_HIFCR_CTCIF7, + }; + + /** + * Masks the 5 interrupt bits for stream 0. This may be used to mask any stream's + * interrupt status by shifting over the stream's interrupt bits over by some + * amount. + */ + static constexpr uint32_t INTERRUPT_STATUS_MSK = + DMA_LISR_TCIF0 | DMA_LISR_HTIF0 | DMA_LISR_TEIF0 | DMA_LISR_DMEIF0 | DMA_LISR_FEIF0; + + /** + * When shifted over and masked using `INTERRUPT_STATUS_MSK`, defines the interrupt + * status flags for a particular stream. + */ + enum class InterruptStatusMsks : uint32_t + { + TRANSFER_COMPLETE = DMA_LISR_TCIF0, + HALF_TRANSFER_COMPLETE = DMA_LISR_HTIF0, + TRANSFER_ERROR = DMA_LISR_TEIF0, + DIRECT_MODE_ERROR = DMA_LISR_DMEIF0, + FIFO_ERROR = DMA_LISR_FEIF0, + }; + + using IrqHandler = void (*)(void); + + /** + * Defines a list of interrupt vectors to be enabled for each DMA stream. + * + * @tparam The DMA identifier. + */ + template + struct Nvic; +}; // class DmaBase + +%% for streams in dma["streams"] +template <> +struct DmaBase::Nvic<{{ streams.instance }}> +{ + static constexpr IRQn_Type DmaIrqs[] { + %% for stream in streams.stream + DMA{{ streams.instance }}_Stream{{ stream.position }}_IRQn, + %% endfor + }; +}; + +%% endfor + +} // namespace platform +} // namespace modm + +#endif // MODM_STM32_DMA_BASE_HPP + diff --git a/src/modm/platform/dma/stm32/f4/dma_hal.hpp.in b/src/modm/platform/dma/stm32/f4/dma_hal.hpp.in new file mode 100644 index 0000000000..2dae436854 --- /dev/null +++ b/src/modm/platform/dma/stm32/f4/dma_hal.hpp.in @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2020, Mike Wolfram + * Copyright (c) 2020, Matthew Arnold + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef DMA_HAL_HPP_ +#define DMA_HAL_HPP_ + +#include +#include + +#include "dma_base.hpp" + +namespace modm +{ +namespace platform +{ +template +class DmaHal : public DmaBase +{ +public: + static_assert(ID >= 1 && ID <= {{ dma.instance | length }}, "dma id invalid"); + + static constexpr uint32_t streamBase() + { +%% for controller in dmaController + %% if controller.dma_controller == 1 + if constexpr (ID == 1) + { + return DMA{{ controller.dma_controller }}_Stream0_BASE; + } + %% else + else if constexpr (ID == {{ controller.dma_controller }}) + { + return DMA{{ controller.dma_controller }}_Stream0_BASE; + } + %% endif +%% endfor + } + + static constexpr DMA_TypeDef *dmaDef() + { +%% for controller in dmaController + %% if controller.dma_controller == 1 + if constexpr (ID == 1) + { + return DMA{{ controller.dma_controller }}; + } + %% else + else if constexpr (ID == {{ controller.dma_controller }}) + { + return DMA{{ controller.dma_controller }}; + } + %% endif +%% endfor + } + + /** + * Sets the bits associated with the stream's interrupt flag clear register to high + * (either LIFCR or HIFCR). + */ + static void clearInterruptFlags(StreamID s) + { + if (uint32_t(s) > uint32_t(StreamID::STREAM_3)) + { + dmaDef()->HIFCR |= dmaStreamIFCRMasks[uint32_t(s)]; + } + else + { + dmaDef()->LIFCR |= dmaStreamIFCRMasks[uint32_t(s)]; + } + } + + /** + * Interrupt flag, use InterruptStatusMsks enum masks to interpret the interrupt flags + */ + static uint32_t getInterruptFlags(StreamID s) + { + static constexpr uint32_t INTERRUPT_STREAM_BLOCK_SIZE = 6; + int shiftOffset = 0; + if (uint32_t(s) >= uint32_t(StreamID::STREAM_4)) + { + shiftOffset = + INTERRUPT_STREAM_BLOCK_SIZE * (uint32_t(s) - uint32_t(StreamID::STREAM_4)); + if (uint32_t(s) >= uint32_t(StreamID::STREAM_6)) + { + shiftOffset += 4; // 4 bytes of padding in the middle of the register + } + + return (dmaDef()->HISR >> shiftOffset) & INTERRUPT_STATUS_MSK; + } + else + { + shiftOffset = INTERRUPT_STREAM_BLOCK_SIZE * uint32_t(s); + if (uint32_t(s) >= uint32_t(StreamID::STREAM_2)) + { + shiftOffset += 4; + } + return (dmaDef()->LISR >> shiftOffset) & INTERRUPT_STATUS_MSK; + } + } +}; // class DmaHal + +/** + * Hardware abstraction of a DMA stream. + * + * TODO + * - Double buffering mode + * + * @note ID must match the DMA number for the stream + */ +template +class DmaStreamHal : public DmaBase +{ + static inline DMA_Stream_TypeDef *streamPtr() + { + return reinterpret_cast(StreamBase) + uint32_t(S); + } + + static constexpr int32_t DMA_DISABLE_TIMEOUT = 2; + +public: + static_assert( +%% for controller in dmaController + StreamBase == DMA{{ controller.dma_controller }}_Stream0_BASE || +%% endfor + true, + "StreamBase invalid"); + + /** + * Configure the DMA stream. + * + * Stops the DMA stream and writes the new values to its control register. + * + * @param[in] channel Specifies the channel used for the specified stream. + * @param[in] direction Specifies if the data will be transferred from + * memory to peripheral, from memory to memory or from peripheral to + * memory. + * @param[in] periphInc Specifies whether the peripheral address register + * should be incremented or not. + * @param[in] memInc Specifies whether the memory address register should + * be incremented or not. + * @param[in] periphDataAlignment Specifies the Peripheral data width. + * @param[in] memDataAlignment Specifies the Memory data width. + * @param[in] mode Specifies the operation mode of the DMAy Streamx. + * @param[in] priority Specifies the software priority for the DMA ID y + * Stream x. + * @param[in] fifoMode Specifies if the FIFO mode or Direct mode will be + * used for the specified stream. + * @param[in] fifoThreshold Specifies the FIFO threshold level. + * @param[in] memBurst Specifies the Burst transfer configuration for the + * memory transfers. It specifies the amount of data to be transferred + * in a single non interruptible transaction. + * @param[in] periphBurst Specifies the burst transfer configuration for the + * peripheral transfers. It specifies the amount of data to be + * transferred in a single non interruptible transaction. + * + * @note The burst mode is possible only if the address increment mode is enabled. + * @note The circular buffer mode cannot be used if the memory-to-memory + * data transfer is configured on the selected stream. + * @note The Direct mode (FIFO mode disabled) cannot be used if the + * memory-to-memory data transfer is configured on the selected stream + * @note The burst mode is possible only if the address increment mode is enabled. + */ + static void configure( + ChannelSelection channel, + DataTransferDirection direction, + PeripheralIncrementMode periphInc, + MemoryIncrementMode memInc, + PeripheralDataSize peripDataSize, + MemoryDataSize memDataSize, + ControlMode mode, + PriorityLevel priority, + FifoMode fifoMode, + FifoThreshold fifoThreshold, + MemoryBurstTransfer memBurst, + PeripheralBurstTransfer periphBurst) + { + disable(); + + uint32_t tmp = streamPtr()->CR; + + tmp &= ((uint32_t) ~( + DMA_SxCR_CHSEL | DMA_SxCR_MBURST | DMA_SxCR_PBURST | + DMA_SxCR_PL | DMA_SxCR_MSIZE | DMA_SxCR_PSIZE | + DMA_SxCR_MINC | DMA_SxCR_PINC | DMA_SxCR_CIRC | + DMA_SxCR_DIR | DMA_SxCR_CT | DMA_SxCR_DBM)); + + tmp |= uint32_t(channel) | uint32_t(direction) | uint32_t(periphInc) | + uint32_t(memInc) | uint32_t(peripDataSize) | uint32_t(memDataSize) | + uint32_t(priority) | uint32_t(mode) | uint32_t(priority); + + if (fifoMode == FifoMode::ENABLED) + { + tmp |= uint32_t(memBurst) | uint32_t(periphBurst); + } + + streamPtr()->CR = tmp; + + tmp = streamPtr()->FCR; + + // Clear direct mode and FIFO threshold bits + tmp &= ~(DMA_SxFCR_DMDIS | DMA_SxFCR_FTH); + + tmp |= uint32_t(fifoMode); + + if (fifoMode == FifoMode::ENABLED) + { + tmp |= uint32_t(fifoThreshold); + + if (memBurst != MemoryBurstTransfer::SINGLE) + { + // Check fifo parameters for compatibility between threshold level and size of + // memory burst + // TODO + } + } + + streamPtr()->FCR = tmp; + } + + static void configureDoubleBufferMode(DoubleBufferMode) + { + // TODO + } + + static void enableInterrupt(Interrupt_t irq) { streamPtr()->CR |= irq.value; } + + static void disableInterrupt(Interrupt_t irq) { streamPtr()->CR &= ~irq.value; } + + /** + * Enable the DMA stream to send/receive. + */ + static void enable() { streamPtr()->CR |= uint32_t(StreamEnableFlag::STREAM_ENABLED); } + + /** + * Disable send/receive on the DMA stream + */ + static bool disable() + { + streamPtr()->CR &= ~uint32_t(StreamEnableFlag::STREAM_ENABLED); + // wait for stream to be stopped + uint32_t start = modm::Clock::now().time_since_epoch().count(); + while ((streamPtr()->CR & uint32_t(StreamEnableFlag::STREAM_ENABLED)) && + modm::Clock::now().time_since_epoch().count() - start > DMA_DISABLE_TIMEOUT) + ; + if (streamPtr()->CR & uint32_t(StreamEnableFlag::STREAM_ENABLED)) + { + return false; + } + return true; + } + + /** + * Get the direction of the data transfer + */ + static DataTransferDirection getDataTransferDirection() + { + return static_cast( + streamPtr()->CR & (DMA_SxCR_DIR_0 | DMA_SxCR_DIR_1)); + } + + /** + * Sets the sources address register based on if the transfer mode is memory to peripheral, + * peripheral to memory, or memory to memory. + */ + static void setSourceAddress(uintptr_t src) + { + if ((streamPtr()->CR & uint32_t(DataTransferDirection::MEM_TO_MEM)) == + uint32_t(DataTransferDirection::MEM_TO_MEM)) + { + streamPtr()->PAR = src; + } + else if ( + (streamPtr()->CR & uint32_t(DataTransferDirection::MEM_TO_PERIPH)) == + uint32_t(DataTransferDirection::MEM_TO_PERIPH)) + { + streamPtr()->M0AR = src; + } + else // peripheral to memory + { + streamPtr()->PAR = src; + } + } + + /** + * Sets the destination address register based on if the transfer mode is memory to peripheral, + * peripheral to memory, or memory to memory. + */ + static void setDestinationAddress(uintptr_t dst) + { + if ((streamPtr()->CR & uint32_t(DataTransferDirection::MEM_TO_MEM)) == + uint32_t(DataTransferDirection::MEM_TO_MEM)) + { + streamPtr()->M0AR = dst; + } + else if ( + (streamPtr()->CR & uint32_t(DataTransferDirection::MEM_TO_PERIPH)) == + uint32_t(DataTransferDirection::MEM_TO_PERIPH)) + { + streamPtr()->PAR = dst; + } + else + { + streamPtr()->M0AR = dst; + } + } + + static void setDataLength(std::size_t length) { streamPtr()->NDTR = length; } +}; // class DmaStreamHal + +} // namespace platform +} // namespace modm + +#endif // MODM_STM32_DMA_HAL_HPP + diff --git a/src/modm/platform/dma/stm32/f4/module.lb b/src/modm/platform/dma/stm32/f4/module.lb new file mode 100644 index 0000000000..d71ec74486 --- /dev/null +++ b/src/modm/platform/dma/stm32/f4/module.lb @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2016-2018, Niklas Hauser +# Copyright (c) 2017, Fabian Greif +# Copyright (c) 2020, Mike Wolfram +# Copyright (c) 2020, Matthew Arnold +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":platform:dma" + module.description = "Direct Memory Access (DMA)" + +def prepare(module, options): + device = options[":target"] + if not device.has_driver("dma:stm32*"): + return False + + # FIXME the driver is for L4 only + if device.identifier["family"] not in ["f4"]: + return False + if device.identifier["name"] not in ["05", "15", "07", "17", "27", "37", "29", "39"]: + return False + + module.depends(":cmsis:device", ":platform:rcc") + + return True + +def build(env): + device = env[":target"] + + properties = device.properties + properties["target"] = device.identifier + dma = device.get_driver("dma") + properties["dma"] = dma + + # Get the peripheral supported by DMA from device info and create a list of signals + # (also determines the maximum number of channels per controller) + + signal_names = [] + streams = {} + controller = [] + + # Each DMA instance has a number of streams, and each stream has a number of channels + for streams in dma["streams"]: + streamController = [] + instance = streams["instance"] + for stream in streams["stream"]: + for channel in stream["channel"]: + channelNum = channel["position"] + streamNum = stream["position"] + drivers = [] + for signal in channel["signal"]: + if "name" in signal: + signal_name = signal["name"].capitalize() + else: + signal_name = "NoSignal" + + if "instance" in signal: + drivers.append({"peripheral": signal["driver"].capitalize() + signal["instance"], "signal": signal_name}) + else: + drivers.append({"peripheral": signal["driver"].capitalize(), "signal": signal_name}) + + if signal_name not in signal_names: + signal_names.append(signal_name) + + streamController.append({ + "stream": int(stream["position"]), + "channel": int(channel["position"]), + "drivers": drivers + }) + + controller.append({ + "dma_controller": int(streams["instance"]), + "stream_ch_mapping": streamController + }) + + signal_names = sorted(list(set(signal_names))) + properties["dmaSignals"] = signal_names + properties["dmaController"] = controller + + env.substitutions = properties + env.outbasepath = "modm/src/modm/platform/dma" + + env.template("dma_base.hpp.in") + env.template("dma_hal.hpp.in") + env.template("dma.hpp.in") + env.template("dma.cpp.in") + diff --git a/src/modm/platform/dma/stm32/dma.cpp.in b/src/modm/platform/dma/stm32/l4/dma.cpp.in similarity index 100% rename from src/modm/platform/dma/stm32/dma.cpp.in rename to src/modm/platform/dma/stm32/l4/dma.cpp.in diff --git a/src/modm/platform/dma/stm32/dma.hpp.in b/src/modm/platform/dma/stm32/l4/dma.hpp.in similarity index 100% rename from src/modm/platform/dma/stm32/dma.hpp.in rename to src/modm/platform/dma/stm32/l4/dma.hpp.in diff --git a/src/modm/platform/dma/stm32/dma_base.hpp.in b/src/modm/platform/dma/stm32/l4/dma_base.hpp.in similarity index 55% rename from src/modm/platform/dma/stm32/dma_base.hpp.in rename to src/modm/platform/dma/stm32/l4/dma_base.hpp.in index 913ddc70ba..7580709594 100644 --- a/src/modm/platform/dma/stm32/dma_base.hpp.in +++ b/src/modm/platform/dma/stm32/l4/dma_base.hpp.in @@ -21,11 +21,7 @@ #include #include -%% if target["family"] == "f4" - %% set reg_prefix = "DMA_SxCR" -%% elif target["family"] in ["f3", "l4"] - %% set reg_prefix = "DMA_CCR" -%% endif +%% set reg_prefix = "DMA_CCR" namespace modm { @@ -43,78 +39,38 @@ class DmaBase { public: // Enums -%% if target["family"] == "f4" - 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_2 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0, - }; - - enum class - MemoryBurstTransfer : uint32_t - { - Single = 0, - Increment4 = DMA_SxCR_MBURST_0, - Increment8 = DMA_SxCR_MBURST_1, - Increment16 = DMA_SxCR_MBURST_1 | DMA_SxCR_MBURST_0, - }; - - enum class - PeripheralBurstTransfer : uint32_t - { - Single = 0, - Increment4 = DMA_SxCR_PBURST_0, - Increment8 = DMA_SxCR_PBURST_1, - Increment16 = DMA_SxCR_PBURST_1 | DMA_SxCR_PBURST_0, - }; - - enum class - FlowControl : uint32_t - { - Dma = 0, - Peripheral = DMA_SxCR_PFCTRL, ///< the peripheral is the flow controller - }; -%% elif target["family"] in ["f3", "l4"] - %% set channel_count = namespace(max_channels = 0) - %% for controller in dmaController - %% if channel_count.max_channels < controller.channels - %% set channel_count.max_channels = controller.channels - %% endif - %% endfor +%% set channel_count = namespace(max_channels = 0) +%% for controller in dmaController + %% if channel_count.max_channels < controller.channels + %% set channel_count.max_channels = controller.channels + %% endif +%% endfor enum class Channel { - %% for channel in range(1, channel_count.max_channels + 1) +%% for channel in range(1, channel_count.max_channels + 1) Channel{{ channel }}{% if channel == 1 %} = 0{% endif %}, - %% endfor +%% endfor }; - %% if target["family"] == "l4" - %% set request_count = namespace(max_requests = 0) - %% for channels in dma["channels"] - %% for channel in channels.channel - %% for request in channel.request - %% if request_count.max_requests < request.position | int - %% set request_count.max_requests = request.position | int - %% endif - %% endfor +%% if target["family"] == "l4" +%% set request_count = namespace(max_requests = 0) +%% for channels in dma["channels"] + %% for channel in channels.channel + %% for request in channel.request + %% if request_count.max_requests < request.position | int + %% set request_count.max_requests = request.position | int + %% endif %% endfor %% endfor +%% endfor enum class Request { - %% for request in range(0, request_count.max_requests + 1) +%% for request in range(0, request_count.max_requests + 1) Request{{ request }}{% if request == 0 %} = 0{% endif %}, - %% endfor +%% endfor }; - %% endif %% endif enum class @@ -174,21 +130,12 @@ public: enum class DataTransferDirection : uint32_t { -%% if target["family"] == "f4" - /// Source: DMA_SxPAR; Sink: DMA_SxM0AR - PeripheralToMemory = 0, - /// Source: DMA_SxM0AR; Sink: DMA_SxPAR - MemoryToPeripheral = DMA_SxCR_DIR_0, - /// Source: DMA_SxPAR; Sink: DMA_SxM0AR - MemoryToMemory = DMA_SxCR_DIR_1, -%% elif target["family"] in ["f3", "l4"] /// Source: DMA_CPARx; Sink: DMA_CMARx PeripheralToMemory = 0, /// Source: DMA_CMARx; Sink: DMA_CPARx MemoryToPeripheral = DMA_CCR_DIR, /// Source: DMA_CPARx; Sink: DMA_CMARx MemoryToMemory = DMA_CCR_MEM2MEM, -%% endif }; /** @@ -203,23 +150,6 @@ public: }; protected: -%% if target["family"] == "f4" - static constexpr uint32_t memoryMask = - DMA_SxCR_MBURST_1 | DMA_SxCR_MBURST_0 | // MemoryBurstTransfer - DMA_SxCR_MSIZE_0 | DMA_SxCR_MSIZE_1 | // MemoryDataSize - DMA_SxCR_MINC | // MemoryIncrementMode - DMA_SxCR_DIR_0 | DMA_SxCR_DIR_1; // DataTransferDirection - static constexpr uint32_t peripheralMask = - DMA_SxCR_PBURST_1 | DMA_SxCR_PBURST_0 | // PeripheralBurstTransfer - DMA_SxCR_PSIZE_0 | DMA_SxCR_PSIZE_1 | // PeripheralDataSize - DMA_SxCR_PINC | // PeripheralIncrementMode - DMA_SxCR_DIR_0 | DMA_SxCR_DIR_1; // DataTransferDirection - static constexpr uint32_t configmask = - DMA_SxCR_CHSEL_2 | DMA_SxCR_CHSEL_1 | DMA_SxCR_CHSEL_0 | // Channel - DMA_SxCR_PL_1 | DMA_SxCR_PL_0 | // Priority - DMA_SxCR_CIRC | // CircularMode - DMA_SxCR_PFCTRL; // FlowControl -%% elif target["family"] in ["f3", "l4"] static constexpr uint32_t memoryMask = DMA_CCR_MSIZE_0 | DMA_CCR_MSIZE_1 | // MemoryDataSize DMA_CCR_MINC | // MemoryIncrementMode @@ -231,7 +161,6 @@ protected: static constexpr uint32_t configmask = DMA_CCR_CIRC | // CircularMode DMA_CCR_PL_1 | DMA_CCR_PL_0; // Priority -%% endif enum class Interrupt { Global = 0x01, diff --git a/src/modm/platform/dma/stm32/dma_hal.hpp.in b/src/modm/platform/dma/stm32/l4/dma_hal.hpp.in similarity index 100% rename from src/modm/platform/dma/stm32/dma_hal.hpp.in rename to src/modm/platform/dma/stm32/l4/dma_hal.hpp.in diff --git a/src/modm/platform/dma/stm32/dma_hal_impl.hpp.in b/src/modm/platform/dma/stm32/l4/dma_hal_impl.hpp.in similarity index 82% rename from src/modm/platform/dma/stm32/dma_hal_impl.hpp.in rename to src/modm/platform/dma/stm32/l4/dma_hal_impl.hpp.in index 74368fd20a..6c41dbfb60 100644 --- a/src/modm/platform/dma/stm32/dma_hal_impl.hpp.in +++ b/src/modm/platform/dma/stm32/l4/dma_hal_impl.hpp.in @@ -21,11 +21,7 @@ modm::platform::DmaChannelHal::start() { DMA_Channel_TypeDef *Base = (DMA_Channel_TypeDef *) CHANNEL_BASE; -%% if target["family"] == "f4" - Base->CR |= DMA_SxCR_EN; -%% else Base->CCR |= DMA_CCR_EN; -%% endif } template @@ -34,13 +30,8 @@ modm::platform::DmaChannelHal::stop() { DMA_Channel_TypeDef *Base = (DMA_Channel_TypeDef *) CHANNEL_BASE; -%% if target["family"] == "f4" - Base->CR &= ~DMA_SxCR_EN; - while (Base->SCR & DMA_SxCR_EN); // wait for stream to be stopped -%% else Base->CCR &= ~DMA_CCR_EN; while (Base->CCR & DMA_CCR_EN); // wait for stream to be stopped -%% endif } template @@ -50,9 +41,5 @@ modm::platform::DmaChannelHal::getDataTransferDirection DMA_Channel_TypeDef *Base = (DMA_Channel_TypeDef *) CHANNEL_BASE; return static_cast( -%% if target["family"] == "f4" - Base->CR & (DMA_SxCR_DIR_0 | DMA_SxCR_DIR_1)); -%% else Base->CCR & (DMA_CCR_MEM2MEM | DMA_CCR_DIR)); -%% endif } diff --git a/src/modm/platform/dma/stm32/module.lb b/src/modm/platform/dma/stm32/l4/module.lb similarity index 100% rename from src/modm/platform/dma/stm32/module.lb rename to src/modm/platform/dma/stm32/l4/module.lb diff --git a/src/modm/platform/spi/stm32/module.lb b/src/modm/platform/spi/stm32/module.lb index 783b3eb8a3..c8805af74b 100644 --- a/src/modm/platform/spi/stm32/module.lb +++ b/src/modm/platform/spi/stm32/module.lb @@ -30,6 +30,7 @@ class Instance(Module): def prepare(self, module, options): module.depends(":platform:spi") + self.device = options[":target"] return True def build(self, env): @@ -43,7 +44,7 @@ class Instance(Module): env.template("spi_hal_impl.hpp.in", "spi_hal_{}_impl.hpp".format(self.instance)) env.template("spi_master.hpp.in", "spi_master_{}.hpp".format(self.instance)) env.template("spi_master.cpp.in", "spi_master_{}.cpp".format(self.instance)) - if env.has_module(":platform:dma"): + if env.has_module(":platform:dma") and self.device.identifier["family"] in ["l4"]: env.template("spi_master_dma.hpp.in", "spi_master_{}_dma.hpp".format(self.instance)) env.template("spi_master_dma_impl.hpp.in", "spi_master_{}_dma_impl.hpp".format(self.instance)) diff --git a/src/modm/platform/uart/stm32/module.lb b/src/modm/platform/uart/stm32/module.lb index fe2ee2aeec..4cc804472f 100644 --- a/src/modm/platform/uart/stm32/module.lb +++ b/src/modm/platform/uart/stm32/module.lb @@ -42,6 +42,11 @@ class Instance(Module): description="", minimum=1, maximum=2 ** 16 - 2, default=16)) + module.add_option( + BooleanOption( + name="usedma", + description="", + default=False)) return True @@ -58,8 +63,12 @@ class Instance(Module): env.template("uart_hal.hpp.in", "uart_hal_{}.hpp".format(self.instance)) env.template("uart_hal_impl.hpp.in", "uart_hal_{}_impl.hpp".format(self.instance)) - env.template("uart.hpp.in", "uart_{}.hpp".format(self.instance)) - env.template("uart.cpp.in", "uart_{}.cpp".format(self.instance)) + if env["usedma"] and env.has_module(":platform:dma"): + env.template("uart_dma.hpp.in", "uart_{}.hpp".format(self.instance)) + env.template("uart_dma.cpp.in", "uart_{}.cpp".format(self.instance)) + else: + env.template("uart.hpp.in", "uart_{}.hpp".format(self.instance)) + env.template("uart.cpp.in", "uart_{}.cpp".format(self.instance)) props["instances"].append(self.instance) diff --git a/src/modm/platform/uart/stm32/uart_base.hpp.in b/src/modm/platform/uart/stm32/uart_base.hpp.in index 917d6309d1..81c343aa84 100644 --- a/src/modm/platform/uart/stm32/uart_base.hpp.in +++ b/src/modm/platform/uart/stm32/uart_base.hpp.in @@ -68,6 +68,8 @@ public: TxComplete = USART_CR1_TCIE, /// Call interrupt when char received (RXNE) or overrun occurred (ORE) RxNotEmpty = USART_CR1_RXNEIE, + /// Set if idle flag set. + RxIdle = USART_CR1_IDLEIE, }; MODM_FLAGS32(Interrupt); diff --git a/src/modm/platform/uart/stm32/uart_dma.cpp.in b/src/modm/platform/uart/stm32/uart_dma.cpp.in new file mode 100644 index 0000000000..2e3ba37033 --- /dev/null +++ b/src/modm/platform/uart/stm32/uart_dma.cpp.in @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020 Matthew Arnold + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +%% set peripheral = uart_name | upper ~ id + +#include "uart_{{ id }}.hpp" + +MODM_ISR({{ peripheral }}) +{ + modm::platform::uart{{ id }}IrqHandler(); +} + diff --git a/src/modm/platform/uart/stm32/uart_dma.hpp.in b/src/modm/platform/uart/stm32/uart_dma.hpp.in new file mode 100644 index 0000000000..d217811842 --- /dev/null +++ b/src/modm/platform/uart/stm32/uart_dma.hpp.in @@ -0,0 +1,239 @@ +/* + * Copyright (c) 2020 Matthew Arnold + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +%% set name = uart_name ~ id +%% set hal = uart_name ~ "Hal" ~ id +%% set peripheral = uart_name | upper ~ id + +#ifndef MODM_STM32_UART_{{ id }}_DMA_HPP +#define MODM_STM32_UART_{{ id }}_DMA_HPP + +#include +#include +#include "uart_hal_{{ id }}.hpp" + +#include + +namespace modm +{ +namespace platform +{ +/** + * Interrupt handler callback for USART{{ id }}_isr. + */ +__attribute__((weak)) void uart{{ id }}IrqHandler(void); +/** + * Interrupt handlers for {{ peripheral }} DMA TX and RX streams. + */ +__attribute__((weak)) void uart{{ id }}DmaTxComplete(void); +__attribute__((weak)) void uart{{ id }}DmaTxError(void); +__attribute__((weak)) void uart{{ id }}DmaRxComplete(void); +__attribute__((weak)) void uart{{ id }}DmaRxError(void); + +template < + typename DmaStreamTx, + DmaBase::ChannelSelection ChannelIdRx, + typename DmaStreamRx, + DmaBase::ChannelSelection ChannelIdTx> +class {{ name }} : public UartBase, public ::modm::Uart +{ +private: + using TxStream = typename DmaStreamTx::template RequestMapping::Stream; + using RxStream = typename DmaStreamRx::template RequestMapping::Stream; + +public: + template