/*************************************************************************** * Copyright (C) 2023 by Federico Amedeo Izzo IU2NUO, * * Niccolò Izzo IU2KIN * * Frederik Saraci IU2NRO * * Silvano Seva IU2KWO * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see * ***************************************************************************/ #ifndef DMA_STREAM_H #define DMA_STREAM_H #include #include #include /** * Enumerating type describing the memory and peripheral data sizes allowed * for a DMA transfer. */ enum class DataSize { _8BIT = 0, ///< 8-bit data size _16BIT = 1, ///< 16-bit data size _32BIT = 2, ///< 32-bit data size }; /** * Stream handler class for STM32F4 DMA Stream peripheral. */ class StreamHandler { public: /** * Constructor. * * @param s: pointer to the DMA stream peripheral. * @param IRQn: DMA stream IRQ number. */ StreamHandler(DMA_Stream_TypeDef *s, IRQn_Type IRQn) : IRQn(IRQn), stream(s) { } /** * Destructor. */ ~StreamHandler() { stream->CR = 0; NVIC_DisableIRQ(IRQn); } /** * Start a DMA stream. * * @param periph: pointer to source/target peripheral. * @param memory: pointer to source/target memory. * @param size: transfer size, in elements. * @param circ: if true the stream runs in circular double buffered mode. */ void start(volatile void *periph, void *memory, const size_t size, const bool circ = false) { uint32_t circFlags = DMA_SxCR_CIRC // Circular buffer mode | DMA_SxCR_HTIE; // Half transfer interrupt if(circ) stream->CR |= circFlags; else stream->CR &= ~circFlags; transferSize = size; stream->NDTR = size; stream->PAR = reinterpret_cast< uint32_t >(periph); stream->M0AR = reinterpret_cast< uint32_t >(memory); stream->CR |= DMA_SxCR_EN; } /** * Get a pointer to the currently "idle" section of a DMA stream that is, * the section not being read by the DMA. * A call to this function is meaningful only if the stream is running in * circular double buffered mode. * * @return address of the idle section or NULL if the stream is not in * circular mode. */ void *idleBuf() { if((stream->CR & DMA_SxCR_CIRC) == 0) return NULL; miosix::FastInterruptDisableLock dLock; uint32_t curPos = stream->NDTR; uint32_t idle = stream->M0AR; uint32_t size = (stream->CR >> DMA_SxCR_MSIZE_Pos) & 0x03; if(curPos > (transferSize / 2)) idle += (transferSize / 2) * size * 2; return reinterpret_cast< void * >(idle); } /** * Syncronize the execution flow with the stream transfer, blocking function. * For cirular buffer mode the calling thread is put to sleep until the DMA * reaches either the half or the end of the transfer, whichever comes first. * For linear buffer mode the calling thread is put to sleep until the DMA * reaches the end of the transfer. * * @return true if thread was effectively put to sleep, false otherwise. */ bool sync() { using namespace miosix; // Enter in critical section until the end of the function FastInterruptDisableLock dLock; Thread *curThread = Thread::IRQgetCurrentThread(); if((waiting != 0) && (waiting != curThread)) return false; waiting = curThread; do { Thread::IRQwait(); { // Re-enable interrupts while waiting for IRQ FastInterruptEnableLock eLock(dLock); Thread::yield(); } } while(waiting != 0); waiting = 0; return true; } /** * Stop an ongoing DMA stream. * The stream does not stop immediately but only when it reaches the half * or the end of the transfer. */ inline void stop() { stopTransfer = true; } /** * Forcefully stop an ongoing DMA stream. * Calling this function causes the immediate stop of an ongoing stream. */ inline void halt() { stopTransfer = true; NVIC_SetPendingIRQ(IRQn); } /** * Query che the current status of the stream. * * @return true if the stream is active, false otherwise. */ inline bool running() { return (stream->CR & DMA_SxCR_EN) ? true : false; } /** * Register a function to be called on stream end. * * @param callback: std::function for the stream end callback. */ inline void setEndTransferCallback(std::function&& callback) { streamEndCallback = callback; } /** * Stream interrupt handler function. * * @param irqFlags: IRQ status flags. */ void IRQhandler(const uint32_t irqFlags) { (void) irqFlags; using namespace miosix; if(((stream->CR & DMA_SxCR_CIRC) == 0) || (stopTransfer == true)) { stream->CR &= ~DMA_SxCR_EN; stopTransfer = false; if(streamEndCallback) streamEndCallback(); } // Wake up eventual pending threads if(waiting == 0) return; waiting->IRQwakeup(); if(waiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) Scheduler::IRQfindNextThread(); waiting = 0; } private: bool stopTransfer; size_t transferSize; const IRQn_Type IRQn; std::function streamEndCallback; DMA_Stream_TypeDef *stream; miosix::Thread *waiting; }; /** * Stream class for STM32F4 DMA Stream peripheral. * * @tparam DMA: base address of the DMA peripheral. * @tparam STN: DMA stream number. * @tparam CH: data channel of the DMA stream. * @tparam PL: priority level of the DMA stream. */ template < uint32_t DMA, uint8_t STN, uint8_t CH, uint8_t PL > class DmaStream { public: /** * Initialize a DMA stream without starting it. * * @param irqPrio: priority of the DMA stream interrupts. * @param ds: data size for both source and destination locations. * @param memToPer: if set to true, transfer goes from memory to peripheral. * @return a StreamHandler object to manage the DMA stream. */ static StreamHandler init(const uint8_t irqPrio, const DataSize ds, const bool memToPer = false) { enableClock(); uint32_t dir = memToPer ? DMA_SxCR_DIR_0 : 0; uint32_t TS = static_cast < uint8_t >(ds); auto stream = getStream(); stream->CR = (CH << 25) // Channel | (PL << 16) // Priority | (TS << 13) // Source transfer size | (TS << 11) // Destination transfer size | DMA_SxCR_MINC // Increment source pointer | dir // Direction | DMA_SxCR_TCIE // Transfer complete interrupt | DMA_SxCR_TEIE; // Transfer error interrupt // Enable DMA interrupts IRQn_Type irq = IRQn(); NVIC_ClearPendingIRQ(irq); NVIC_SetPriority(irq, irqPrio); NVIC_EnableIRQ(irq); return StreamHandler(stream, irq); } /** * Low-level shutdown of a DMA stream. */ static inline void terminate() { NVIC_DisableIRQ(IRQn()); getStream()->CR = 0; } /** * Stream IRQ handler, forwards the call to the handler inside a StreamHandler * class. * * @param hdl: pointer to the StreamHandler class managing the stream. */ static inline void IRQhandleInterrupt(StreamHandler *hdl) { uint32_t flags = readIrqFlags(); hdl->IRQhandler(flags); clearIrqFlags(flags); } private: /** * Enable DMA AHB clock. */ static inline constexpr void enableClock() { if(reinterpret_cast< DMA_TypeDef *>(DMA) == DMA1) RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; else RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; RCC_SYNC(); } /** * Obtain pointer to the DMA stream peripheral given the DMA and the stream * number. */ static inline constexpr DMA_Stream_TypeDef *getStream() { DMA_Stream_TypeDef *base; if(reinterpret_cast< DMA_TypeDef *>(DMA) == DMA1) base = DMA1_Stream0; else base = DMA2_Stream0; return base + STN; } /** * Clear the IRQ flags of a DMA stream according to a given mask. */ static inline constexpr void clearIrqFlags(const uint32_t mask) { uint32_t shift = (STN % 4) * 8; if(STN < 4) reinterpret_cast< DMA_TypeDef *>(DMA)->LIFCR = (mask << shift); else reinterpret_cast< DMA_TypeDef *>(DMA)->HIFCR = (mask << shift); } /** * Get the current IRQ flags of a DMA stream. */ static inline constexpr uint32_t readIrqFlags() { uint32_t shift = (STN % 4) * 8; uint32_t flags = 0; if(STN < 4) flags = reinterpret_cast< DMA_TypeDef *>(DMA)->LISR; else flags = reinterpret_cast< DMA_TypeDef *>(DMA)->HISR; return (flags >> shift) & 0x7D; } /** * Get the IRQ number of a DMA stream. */ static inline constexpr IRQn_Type IRQn() { // // NOTE: there is no better implementation than the one below because // DMA stream IRQ numbers are not contiguous... // if(reinterpret_cast< DMA_TypeDef *>(DMA) == DMA1) { switch(STN) { case 0: return DMA1_Stream0_IRQn; break; case 1: return DMA1_Stream1_IRQn; break; case 2: return DMA1_Stream2_IRQn; break; case 3: return DMA1_Stream3_IRQn; break; case 4: return DMA1_Stream4_IRQn; break; case 5: return DMA1_Stream5_IRQn; break; case 6: return DMA1_Stream6_IRQn; break; case 7: return DMA1_Stream7_IRQn; break; } } else { switch(STN) { case 0: return DMA2_Stream0_IRQn; break; case 1: return DMA2_Stream1_IRQn; break; case 2: return DMA2_Stream2_IRQn; break; case 3: return DMA2_Stream3_IRQn; break; case 4: return DMA2_Stream4_IRQn; break; case 5: return DMA2_Stream5_IRQn; break; case 6: return DMA2_Stream6_IRQn; break; case 7: return DMA2_Stream7_IRQn; break; } } return DMA1_Stream0_IRQn; } }; #endif /* DMA_STREAM_H */