diff --git a/meson.build b/meson.build
index be1023f2..9c505e82 100644
--- a/meson.build
+++ b/meson.build
@@ -337,7 +337,7 @@ mod17_src = src + stm32f405_src + ['platform/targets/Module17/platform.c',
'platform/drivers/NVM/nvmem_Mod17.c',
'platform/drivers/baseband/radio_Mod17.cpp',
'platform/drivers/audio/inputStream_Mod17.cpp',
- 'platform/drivers/audio/outputStream_Mod17.c',
+ 'platform/drivers/audio/outputStream_Mod17.cpp',
'platform/drivers/audio/audio_Mod17.c',
'platform/drivers/baseband/MCP4551_Mod17.cpp']
diff --git a/platform/drivers/audio/outputStream_MDx.cpp b/platform/drivers/audio/outputStream_MDx.cpp
index 2c6543d5..5f23f4f4 100644
--- a/platform/drivers/audio/outputStream_MDx.cpp
+++ b/platform/drivers/audio/outputStream_MDx.cpp
@@ -68,8 +68,7 @@ void __attribute__((used)) DMA_Handler()
else
idleBuf = bufAddr + (bufLen / 2);
- // Stop transfer for linear buffer mode, pending termination request or
- // DMA error.
+ // Stop transfer for linear buffer mode or pending termination request.
if((circularMode == false) || (reqFinish == true))
{
stopTransfer();
@@ -80,7 +79,7 @@ void __attribute__((used)) DMA_Handler()
| DMA_LIFCR_CHTIF2
| DMA_LIFCR_CTEIF2;
- // Finally, wake up eventual pending threads
+ // Finally, wake up eventual pending threads
if(dmaWaiting == 0) return;
dmaWaiting->IRQwakeup();
if(dmaWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
diff --git a/platform/drivers/audio/outputStream_Mod17.c b/platform/drivers/audio/outputStream_Mod17.c
deleted file mode 100644
index d454fe43..00000000
--- a/platform/drivers/audio/outputStream_Mod17.c
+++ /dev/null
@@ -1,198 +0,0 @@
-/***************************************************************************
- * Copyright (C) 2021 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 *
- ***************************************************************************/
-
-#include
-#include
-#include
-#include
-
-int priority = PRIO_BEEP;
-bool running = false;
-
-void stopTransfer()
-{
- /* Shutdown timer */
- TIM7->CR1 &= ~TIM_CR1_CEN;
-
- /* Disable DAC channels and clear underrun flags */
- DAC->CR &= ~(DAC_CR_EN1 | DAC_CR_EN2);
- DAC->SR |= DAC_SR_DMAUDR1 | DAC_SR_DMAUDR2;
-
- /* Stop DMA transfers */
- DMA1_Stream5->CR &= ~DMA_SxCR_EN;
- DMA1_Stream6->CR &= ~DMA_SxCR_EN;
-
- running = false;
-}
-
-/*
- * DMA 1, Stream 5: data transfer for RTX sink
- */
-void __attribute__((used)) DMA1_Stream5_IRQHandler()
-{
- stopTransfer();
-
- DMA1->HIFCR |= DMA_HIFCR_CTCIF5
- | DMA_HIFCR_CTEIF5;
-
- NVIC_DisableIRQ(DMA1_Stream5_IRQn);
-}
-
-/*
- * DMA 1, Stream 6: data transfer for speaker sink
- */
-void __attribute__((used)) DMA1_Stream6_IRQHandler()
-{
- stopTransfer();
-
- DMA1->HIFCR |= DMA_HIFCR_CTCIF6
- | DMA_HIFCR_CTEIF6;
-
- NVIC_DisableIRQ(DMA1_Stream6_IRQn);
-}
-
-streamId outputStream_start(const enum AudioSink destination,
- const enum AudioPriority prio,
- stream_sample_t * const buf,
- const size_t length,
- const uint32_t sampleRate)
-{
- if(destination == SINK_MCU) return -1; /* This device cannot sink to buffer */
- if(running) /* Check if a stream is already running */
- {
- if(prio < priority) return -1; /* Requested priority is lower than current */
- if(prio > priority) stopTransfer(); /* Stop an ongoing stream with lower priority */
- while(running) ; /* Same priority, wait for current stream to end */
- }
-
- /* This assigment must be thread-safe */
- __disable_irq();
- priority = prio;
- running = true;
- __enable_irq();
-
- /*
- * Convert buffer elements from int16_t to unsigned 16 bit values ranging
- * from 0 to 4096, as required by DAC. Processing can be done in-place
- * because the API mandates that the function caller does not modify the
- * buffer content once this function has been called. Code below exploits
- * Cortex M4 SIMD instructions for fast execution.
- */
- uint32_t *data = ((uint32_t *) buf);
- for(size_t i = 0; i < length/2; i++)
- {
- uint32_t value = __SADD16(data[i], 0x80008000);
- data[i] = (value >> 4) & 0x0FFF0FFF;
- }
-
- /* Handle last element in case of odd buffer length */
- if((length % 2) != 0)
- {
- int16_t value = buf[length - 1] + 32768;
- buf[length - 1] = ((uint16_t) value) >> 4;
- }
-
- /* Configure GPIOs */
- gpio_setMode(BASEBAND_TX, INPUT_ANALOG); /* Baseband TX */
- gpio_setMode(AUDIO_SPK, INPUT_ANALOG); /* Spk output */
-
- /*
- * Enable peripherals
- */
- RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
- RCC->APB1ENR |= RCC_APB1ENR_DACEN
- | RCC_APB1ENR_TIM7EN;
- __DSB();
-
- /*
- * Configure DAC and DMA stream
- */
- if(destination == SINK_RTX)
- {
- DAC->CR = DAC_CR_DMAEN1 /* Enable DMA mode */
- | DAC_CR_TSEL1_1 /* TIM7 TRGO as trigger source */
- | DAC_CR_TEN1 /* Enable trigger input */
- | DAC_CR_EN1; /* Enable DAC */
-
- DMA1_Stream5->NDTR = length;
- DMA1_Stream5->PAR = ((uint32_t) &(DAC->DHR12R1));
- DMA1_Stream5->M0AR = ((uint32_t) buf);
- DMA1_Stream5->CR = DMA_SxCR_CHSEL /* Channel 7 */
- | DMA_SxCR_PL /* Very high priority */
- | DMA_SxCR_MSIZE_0 /* 16 bit source size */
- | DMA_SxCR_PSIZE_0 /* 16 bit destination size */
- | DMA_SxCR_MINC /* Increment source pointer */
- | DMA_SxCR_TCIE /* Transfer complete interrupt */
- | DMA_SxCR_TEIE /* Transfer error interrupt */
- | DMA_SxCR_DIR_0 /* Memory to peripheral */
- | DMA_SxCR_EN; /* Start transfer */
-
- NVIC_ClearPendingIRQ(DMA1_Stream5_IRQn);
- NVIC_SetPriority(DMA1_Stream5_IRQn, 10);
- NVIC_EnableIRQ(DMA1_Stream5_IRQn);
- }
- else
- {
- DAC->CR = DAC_CR_DMAEN2 /* Enable DMA mode */
- | DAC_CR_TSEL2_1 /* TIM7 TRGO as trigger source */
- | DAC_CR_TEN2 /* Enable trigger input */
- | DAC_CR_EN2; /* Enable DAC */
-
- DMA1_Stream6->NDTR = length;
- DMA1_Stream6->PAR = ((uint32_t) &(DAC->DHR12R2));
- DMA1_Stream6->M0AR = ((uint32_t) buf);
- DMA1_Stream6->CR = DMA_SxCR_CHSEL /* Channel 7 */
- | DMA_SxCR_PL /* Very high priority */
- | DMA_SxCR_MSIZE_0 /* 16 bit source size */
- | DMA_SxCR_PSIZE_0 /* 16 bit destination size */
- | DMA_SxCR_MINC /* Increment source pointer */
- | DMA_SxCR_TCIE /* Transfer complete interrupt */
- | DMA_SxCR_TEIE /* Transfer error interrupt */
- | DMA_SxCR_DIR_0 /* Memory to peripheral */
- | DMA_SxCR_EN; /* Start transfer */
-
- NVIC_ClearPendingIRQ(DMA1_Stream6_IRQn);
- NVIC_SetPriority(DMA1_Stream6_IRQn, 10);
- NVIC_EnableIRQ(DMA1_Stream6_IRQn);
- }
-
- /*
- * TIM7 for conversion triggering via TIM7_TRGO, that is counter reload.
- * AP1 frequency is 42MHz but timer runs at 84MHz, tick rate is 1MHz,
- * reload register is configured based on desired sample rate.
- */
- TIM7->PSC = 83;
- TIM7->ARR = (1000000/sampleRate) - 1;
- TIM7->CNT = 0;
- TIM7->EGR = TIM_EGR_UG;
- TIM7->CR2 = TIM_CR2_MMS_1;
- TIM7->CR1 = TIM_CR1_CEN;
-
- return 0;
-}
-
-void outputStream_stop(streamId id)
-{
- (void) id;
-
- if(!running) return;
-
- stopTransfer();
-}
diff --git a/platform/drivers/audio/outputStream_Mod17.cpp b/platform/drivers/audio/outputStream_Mod17.cpp
new file mode 100644
index 00000000..358cef40
--- /dev/null
+++ b/platform/drivers/audio/outputStream_Mod17.cpp
@@ -0,0 +1,310 @@
+/***************************************************************************
+ * Copyright (C) 2021 - 2022 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 *
+ ***************************************************************************/
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+static int priority = PRIO_BEEP;
+static bool running = false; // Stream is running
+static bool circularMode = false; // Circular mode enabled
+static bool reqFinish = false; // Pending termination request
+static size_t bufLen = 0; // Buffer length
+static stream_sample_t *bufAddr = 0; // Start address of data buffer, fixed.
+static stream_sample_t *idleBuf = 0;
+
+using namespace miosix;
+static Thread *dmaWaiting = 0;
+
+
+/**
+ * \internal
+ * Stop an ongoing transfer, deactivating timers and DMA stream.
+ */
+void stopTransfer()
+{
+ // Shutdown timer
+ TIM7->CR1 &= ~TIM_CR1_CEN;
+
+ // Disable DAC channels and clear underrun flags
+ DAC->CR &= ~(DAC_CR_EN1 | DAC_CR_EN2);
+ DAC->SR |= DAC_SR_DMAUDR1 | DAC_SR_DMAUDR2;
+
+ // Stop DMA transfers
+ DMA1_Stream5->CR &= ~DMA_SxCR_EN;
+ DMA1_Stream6->CR &= ~DMA_SxCR_EN;
+
+ running = false;
+}
+
+/**
+ * \internal
+ * Actual implementation of DMA interrupt handler.
+ */
+void __attribute__((used)) DMA_Handler()
+{
+ // Manage half transfer interrupt
+ if((DMA1->HISR & DMA_HISR_HTIF5) || (DMA1->HISR & DMA_HISR_HTIF6))
+ idleBuf = bufAddr;
+ else
+ idleBuf = bufAddr + (bufLen / 2);
+
+ // Stop transfer for linear buffer mode or pending termination request.
+ if((circularMode == false) || (reqFinish == true))
+ {
+ stopTransfer();
+ }
+
+ // Clear interrupt flags for stream 5
+ if(DMA1->HISR & 0x00000F40)
+ {
+ DMA1->HIFCR = DMA_HIFCR_CTEIF5
+ | DMA_HIFCR_CTCIF5
+ | DMA_HIFCR_CHTIF5;
+ }
+
+ // Clear interrupt flags for stream 6
+ if(DMA1->HISR & 0x003D0000)
+ {
+ DMA1->HIFCR = DMA_HIFCR_CTEIF6
+ | DMA_HIFCR_CTCIF6
+ | DMA_HIFCR_CHTIF6;
+ }
+
+ // Finally, wake up eventual pending threads
+ if(dmaWaiting == 0) return;
+ dmaWaiting->IRQwakeup();
+ if(dmaWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority())
+ Scheduler::IRQfindNextThread();
+ dmaWaiting = 0;
+}
+
+// DMA 1, Stream 5: data transfer for RTX sink
+void __attribute__((used)) DMA1_Stream5_IRQHandler()
+{
+ saveContext();
+ asm volatile("bl _Z11DMA_Handlerv");
+ restoreContext();
+}
+
+// DMA 1, Stream 6: data transfer for speaker sink
+void __attribute__((used)) DMA1_Stream6_IRQHandler()
+{
+ saveContext();
+ asm volatile("bl _Z11DMA_Handlerv");
+ restoreContext();
+}
+
+
+streamId outputStream_start(const enum AudioSink destination,
+ const enum AudioPriority prio,
+ stream_sample_t * const buf,
+ const size_t length,
+ const enum BufMode mode,
+ const uint32_t sampleRate)
+{
+ // Sanity check
+ if((buf == NULL) || (length == 0) || (sampleRate == 0)) return -1;
+
+ // This device cannot sink to buffers
+ if(destination == SINK_MCU) return -1;
+
+ // Check if an output stream is already opened and, in case, handle priority.
+ if(running)
+ {
+ if(prio < priority) return -1; // Lower priority, reject.
+ if(prio > priority) stopTransfer(); // Higher priority, takes over.
+ while(running) ; // Same priority, wait.
+ }
+
+ // Thread-safe block: assign priority, set stream as running and lock "beeps"
+ __disable_irq();
+ priority = prio;
+ running = true;
+ __enable_irq();
+
+ /*
+ * Convert buffer elements from int16_t to unsigned 8 bit values, as
+ * required by tone generator. Processing can be done in-place because the
+ * API mandates that the function caller does not modify the buffer content
+ * once this function has been called.
+ */
+ S16toU12(buf, length);
+ bufAddr = buf;
+ bufLen = length;
+ idleBuf = bufAddr + (bufLen / 2);
+
+ // Configure GPIOs
+ gpio_setMode(BASEBAND_TX, INPUT_ANALOG); /* Baseband TX */
+ gpio_setMode(AUDIO_SPK, INPUT_ANALOG); /* Spk output */
+
+ /*
+ * Enable peripherals
+ */
+ RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN;
+ RCC->APB1ENR |= RCC_APB1ENR_DACEN
+ | RCC_APB1ENR_TIM7EN;
+ __DSB();
+
+ /*
+ * Configure DAC and DMA stream
+ */
+ uint32_t circular = 0;
+ if(mode == BUF_CIRC_DOUBLE)
+ {
+ circular = DMA_SxCR_CIRC // Circular buffer mode
+ | DMA_SxCR_HTIE; // Half transfer interrupt
+ circularMode = true;
+ }
+
+ if(destination == SINK_RTX)
+ {
+ DAC->CR = DAC_CR_DMAEN1 // Enable DMA mode
+ | DAC_CR_TSEL1_1 // TIM7 TRGO as trigger source
+ | DAC_CR_TEN1 // Enable trigger input
+ | DAC_CR_EN1; // Enable DAC
+
+ DMA1_Stream5->NDTR = length;
+ DMA1_Stream5->PAR = reinterpret_cast< uint32_t >(&(DAC->DHR12R1));
+ DMA1_Stream5->M0AR = reinterpret_cast< uint32_t >(buf);
+ DMA1_Stream5->CR = DMA_SxCR_CHSEL // Channel 7
+ | DMA_SxCR_PL // Very high priority
+ | DMA_SxCR_MSIZE_0 // 16 bit source size
+ | DMA_SxCR_PSIZE_0 // 16 bit destination size
+ | DMA_SxCR_MINC // Increment source pointer
+ | DMA_SxCR_TCIE // Transfer complete interrupt
+ | DMA_SxCR_TEIE // Transfer error interrupt
+ | DMA_SxCR_DIR_0 // Memory to peripheral
+ | circular // Circular mode
+ | DMA_SxCR_EN; // Start transfer
+
+ NVIC_ClearPendingIRQ(DMA1_Stream5_IRQn);
+ NVIC_SetPriority(DMA1_Stream5_IRQn, 10);
+ NVIC_EnableIRQ(DMA1_Stream5_IRQn);
+ }
+ else
+ {
+ DAC->CR = DAC_CR_DMAEN2 // Enable DMA mode
+ | DAC_CR_TSEL2_1 // TIM7 TRGO as trigger source
+ | DAC_CR_TEN2 // Enable trigger input
+ | DAC_CR_EN2; // Enable DAC
+
+ DMA1_Stream6->NDTR = length;
+ DMA1_Stream6->PAR = reinterpret_cast< uint32_t >(&(DAC->DHR12R2));
+ DMA1_Stream6->M0AR = reinterpret_cast< uint32_t >(buf);
+ DMA1_Stream6->CR = DMA_SxCR_CHSEL // Channel 7
+ | DMA_SxCR_PL // Very high priority
+ | DMA_SxCR_MSIZE_0 // 16 bit source size
+ | DMA_SxCR_PSIZE_0 // 16 bit destination size
+ | DMA_SxCR_MINC // Increment source pointer
+ | DMA_SxCR_TCIE // Transfer complete interrupt
+ | DMA_SxCR_TEIE // Transfer error interrupt
+ | DMA_SxCR_DIR_0 // Memory to peripheral
+ | circular // Circular mode
+ | DMA_SxCR_EN; // Start transfer
+
+ NVIC_ClearPendingIRQ(DMA1_Stream6_IRQn);
+ NVIC_SetPriority(DMA1_Stream6_IRQn, 10);
+ NVIC_EnableIRQ(DMA1_Stream6_IRQn);
+ }
+
+ /*
+ * TIM7 for conversion triggering via TIM7_TRGO, that is counter reload.
+ * AP1 frequency is 42MHz but timer runs at 84MHz, tick rate is 1MHz,
+ * reload register is configured based on desired sample rate.
+ */
+ TIM7->CNT = 0;
+ TIM7->PSC = 0;
+ TIM7->ARR = 84000000/sampleRate;
+ TIM7->EGR = TIM_EGR_UG;
+ TIM7->CR2 = TIM_CR2_MMS_1;
+ TIM7->CR1 = TIM_CR1_CEN;
+
+ return 0;
+}
+
+stream_sample_t *outputStream_getIdleBuffer(const streamId id)
+{
+ (void) id;
+
+ if(!circularMode) return nullptr;
+
+ return idleBuf;
+}
+
+bool outputStream_sync(const streamId id, const bool bufChanged)
+{
+ (void) id;
+
+ if(circularMode && bufChanged)
+ {
+ stream_sample_t *ptr = outputStream_getIdleBuffer(id);
+ S16toU12(ptr, bufLen/2);
+ }
+
+ // Enter in critical section until the end of the function
+ FastInterruptDisableLock dLock;
+
+ Thread *curThread = Thread::IRQgetCurrentThread();
+ if((dmaWaiting != 0) && (dmaWaiting != curThread)) return false;
+ dmaWaiting = curThread;
+
+ do
+ {
+ Thread::IRQwait();
+ {
+ // Re-enable interrupts while waiting for IRQ
+ FastInterruptEnableLock eLock(dLock);
+ Thread::yield();
+ }
+ }
+ while((dmaWaiting != 0) && (running == true));
+
+ dmaWaiting = 0;
+
+ return true;
+}
+
+void outputStream_stop(const streamId id)
+{
+ (void) id;
+
+ reqFinish = true;
+}
+
+void outputStream_terminate(const streamId id)
+{
+ (void) id;
+
+ FastInterruptDisableLock dLock;
+
+ stopTransfer();
+
+ DMA1->HIFCR = DMA_HIFCR_CTEIF5
+ | DMA_HIFCR_CTCIF5
+ | DMA_HIFCR_CHTIF5;
+
+ DMA1->HIFCR = DMA_HIFCR_CTEIF6
+ | DMA_HIFCR_CTCIF6
+ | DMA_HIFCR_CHTIF6;
+}