2023-04-12 20:03:00 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* 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 <http://www.gnu.org/licenses/> *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#ifndef DMA_STREAM_H
|
|
|
|
#define DMA_STREAM_H
|
|
|
|
|
|
|
|
#include <kernel/scheduler/scheduler.h>
|
|
|
|
#include <stm32f4xx.h>
|
|
|
|
#include <miosix.h>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2023-04-15 07:56:15 +00:00
|
|
|
inline void stop()
|
2023-04-12 20:03:00 +00:00
|
|
|
{
|
|
|
|
stopTransfer = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Forcefully stop an ongoing DMA stream.
|
|
|
|
* Calling this function causes the immediate stop of an ongoing stream.
|
|
|
|
*/
|
2023-04-15 07:56:15 +00:00
|
|
|
inline void halt()
|
2023-04-12 20:03:00 +00:00
|
|
|
{
|
|
|
|
stopTransfer = true;
|
|
|
|
NVIC_SetPendingIRQ(IRQn);
|
|
|
|
}
|
|
|
|
|
2023-04-15 07:56:15 +00:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2023-04-12 20:03:00 +00:00
|
|
|
/**
|
|
|
|
* Register a function to be called on stream end.
|
|
|
|
*
|
|
|
|
* @param callback: std::function for the stream end callback.
|
|
|
|
*/
|
2023-04-15 07:56:15 +00:00
|
|
|
inline void setEndTransferCallback(std::function<void()>&& callback)
|
2023-04-12 20:03:00 +00:00
|
|
|
{
|
|
|
|
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<void()> 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.
|
|
|
|
*/
|
2023-04-15 07:56:15 +00:00
|
|
|
static inline void terminate()
|
2023-04-12 20:03:00 +00:00
|
|
|
{
|
|
|
|
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.
|
|
|
|
*/
|
2023-04-15 07:56:15 +00:00
|
|
|
static inline void IRQhandleInterrupt(StreamHandler *hdl)
|
2023-04-12 20:03:00 +00:00
|
|
|
{
|
|
|
|
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 */
|