From 3d04759e8d2f22e5f635f2df59441c9025d90305 Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Sun, 16 Mar 2025 18:35:41 +0100 Subject: [PATCH] Audio: STM32 DAC: extended driver to STM32H7 family --- platform/drivers/audio/stm32_dac.cpp | 64 ++- platform/drivers/audio/stm32_dac.h | 1 - platform/mcu/STM32H7xx/drivers/DmaStream.hpp | 399 +++++++++++++++++++ 3 files changed, 453 insertions(+), 11 deletions(-) create mode 100644 platform/mcu/STM32H7xx/drivers/DmaStream.hpp diff --git a/platform/drivers/audio/stm32_dac.cpp b/platform/drivers/audio/stm32_dac.cpp index 8ccb26c2..d5c39aa8 100644 --- a/platform/drivers/audio/stm32_dac.cpp +++ b/platform/drivers/audio/stm32_dac.cpp @@ -19,14 +19,36 @@ ***************************************************************************/ #include +#include #include #include #include -#include #include #include +#include #include "stm32_dac.h" +#if defined(STM32H743xx) +#include + +#define DAC DAC1 +#define DAC_TRIG_CH1 (11 << 2) // lptim1_out +#define DAC_TRIG_CH2 (12 << 18) // lptim2_out + +typedef Lptim timebase_type; +#define TimebaseCh1 Lptim(LPTIM1_BASE, 168000000) +#define TimebaseCh2 Lptim(LPTIM2_BASE, 168000000) +#else +#include + +#define DAC_TRIG_CH1 0x00 +#define DAC_TRIG_CH2 DAC_CR_TSEL2_1 + +typedef Timer timebase_type; +#define TimebaseCh1 Timer(TIM6_BASE) +#define TimebaseCh2 Timer(TIM7_BASE) +#endif + struct ChannelState { struct streamCtx *ctx; // Current stream context @@ -37,7 +59,7 @@ struct ChannelState struct DacChannel { volatile uint32_t *dacReg; // DAC data register - Timer tim; // TIM peripheral for DAC trigger + timebase_type tim; // TIM peripheral for DAC trigger }; @@ -46,8 +68,8 @@ using Dma1_Stream6 = DmaStream< DMA1_BASE, 6, 7, 3 >; // DMA 1, Stream 6, channe static constexpr DacChannel channels[] = { - {&(DAC->DHR12R1), Timer(TIM6_BASE)}, - {&(DAC->DHR12R2), Timer(TIM7_BASE)}, + {&(DAC->DHR12R1), TimebaseCh1}, + {&(DAC->DHR12R2), TimebaseCh2}, }; #pragma GCC diagnostic ignored "-Wpedantic" @@ -117,30 +139,46 @@ void __attribute__((used)) DMA1_Stream6_IRQHandler() void stm32dac_init(const uint8_t instance, const uint16_t idleLevel) { // Enable peripherals + #if defined(STM32H743xx) + RCC->APB1LENR |= RCC_APB1LENR_DAC12EN; + #else RCC->APB1ENR |= RCC_APB1ENR_DACEN; + #endif switch(instance) { case STM32_DAC_CH1: { + #if defined(STM32H743xx) + RCC->APB1LENR |= RCC_APB1LENR_LPTIM1EN; + RCC->D2CCIP2R |= RCC_D2CCIP2R_LPTIM1SEL_0; + DMAMUX1_Channel5->CCR = 67; + #else RCC->APB1ENR |= RCC_APB1ENR_TIM6EN; + #endif __DSB(); DAC->DHR12R1 = idleLevel; DAC->CR |= DAC_CR_DMAEN1 // Enable DMA - | 0x00 // TIM6 as trigger source for CH1 + | DAC_TRIG_CH1 // Set trigger source for CH1 | DAC_CR_EN1; // Enable CH1 } break; case STM32_DAC_CH2: { + #if defined(STM32H743xx) + RCC->APB4ENR |= RCC_APB4ENR_LPTIM2EN; + RCC->D3CCIPR |= RCC_D3CCIPR_LPTIM2SEL_0; + DMAMUX1_Channel6->CCR = 68; + #else RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; + #endif __DSB(); DAC->DHR12R2 = idleLevel; DAC->CR |= DAC_CR_DMAEN2 // Enable DMA - | DAC_CR_TSEL2_1 // TIM7 as trigger source for CH2 + | DAC_TRIG_CH2 // Set trigger source for CH2 | DAC_CR_EN2; // Enable CH2 } break; @@ -165,9 +203,12 @@ void stm32dac_terminate() } } - RCC->APB1ENR &= ~(RCC_APB1ENR_DACEN | - RCC_APB1ENR_TIM6EN | - RCC_APB1ENR_TIM7EN); + #if defined(STM32H743xx) + RCC->APB1LENR &= ~(RCC_APB1LENR_DAC12EN | RCC_APB1LENR_LPTIM1EN); + RCC->APB4ENR &= ~RCC_APB4ENR_LPTIM2EN; + #else + RCC->APB1ENR &= ~(RCC_APB1ENR_DACEN | RCC_APB1ENR_TIM6EN | RCC_APB1ENR_TIM7EN); + #endif __DSB(); } @@ -196,6 +237,7 @@ static int stm32dac_start(const uint8_t instance, const void *config, * has been called. */ S16toU12(ctx->buffer, ctx->bufSize); + miosix::markBufferBeforeDmaWrite(ctx->buffer, ctx->bufSize*sizeof(int16_t)); bool circ = false; if(ctx->bufMode == BUF_CIRC_DOUBLE) @@ -227,7 +269,9 @@ static int stm32dac_sync(struct streamCtx *ctx, uint8_t dirty) if((ctx->bufMode == BUF_CIRC_DOUBLE) && (dirty != 0)) { void *ptr = state->stream.idleBuf(); - S16toU12(reinterpret_cast< int16_t *>(ptr), ctx->bufSize/2); + size_t writeSize = ctx->bufSize/2; + S16toU12(reinterpret_cast< int16_t *>(ptr), writeSize); + miosix::markBufferBeforeDmaWrite(ctx->buffer, writeSize*sizeof(int16_t)); } bool ok = state->stream.sync(); diff --git a/platform/drivers/audio/stm32_dac.h b/platform/drivers/audio/stm32_dac.h index 57586381..ba4f84c9 100644 --- a/platform/drivers/audio/stm32_dac.h +++ b/platform/drivers/audio/stm32_dac.h @@ -23,7 +23,6 @@ #include #include -#include #include #ifdef __cplusplus diff --git a/platform/mcu/STM32H7xx/drivers/DmaStream.hpp b/platform/mcu/STM32H7xx/drivers/DmaStream.hpp new file mode 100644 index 00000000..e420a23e --- /dev/null +++ b/platform/mcu/STM32H7xx/drivers/DmaStream.hpp @@ -0,0 +1,399 @@ +/*************************************************************************** + * Copyright (C) 2024 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 = (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 */