diff --git a/meson.build b/meson.build
index f2dc1d59..9a8a8228 100644
--- a/meson.build
+++ b/meson.build
@@ -99,6 +99,7 @@ mdx_src = ['platform/drivers/ADC/ADC1_MDx.c',
'platform/drivers/GPS/GPS_MDx.cpp',
'platform/drivers/NVM/W25Qx.c',
'platform/drivers/audio/audio_MDx.c',
+ 'platform/drivers/audio/inputStream_MDx.cpp',
'platform/drivers/baseband/HR_Cx000.cpp',
'platform/drivers/backlight/backlight_MDx.c',
'platform/drivers/tones/toneGenerator_MDx.cpp']
diff --git a/platform/drivers/audio/inputStream_MDx.cpp b/platform/drivers/audio/inputStream_MDx.cpp
new file mode 100644
index 00000000..a6433ab5
--- /dev/null
+++ b/platform/drivers/audio/inputStream_MDx.cpp
@@ -0,0 +1,283 @@
+/***************************************************************************
+ * 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
+#include
+#include
+#include
+
+using namespace miosix;
+
+
+bool inUse = false; // Flag to determine if the input stream is already open.
+Thread *sWaiting = 0; // Thread waiting on interrupt.
+uint32_t bufAddr = 0; // Start address of data buffer, fixed.
+uint32_t bufCurr = 0; // Buffer address to be returned to application.
+size_t bufLen = 0; // Buffer length.
+uint8_t bufMode = BUF_LINEAR; // Buffer management mode.
+
+void __attribute__((used)) DmaHandlerImpl()
+{
+ if(DMA2->LISR & (DMA_LISR_TCIF2 | DMA_LISR_HTIF2))
+ {
+ switch(bufMode)
+ {
+ case BUF_LINEAR:
+ // Finish, stop DMA and ADC
+ DMA2_Stream2->CR &= ~DMA_SxCR_EN;
+ ADC2->CR2 &= ~ADC_CR2_ADON;
+ break;
+
+ case BUF_CIRC_DOUBLE:
+ // Return half of the buffer but do not stop the DMA
+ if(DMA2->LISR & DMA_LISR_HTIF2)
+ bufCurr = bufAddr; // Return first half
+ else
+ bufCurr = bufAddr + (bufLen / 2); // Return second half
+ break;
+
+ default:
+ break;
+ }
+
+ // Wake up the thread
+ if(sWaiting != 0)
+ {
+ sWaiting->IRQwakeup();
+ Priority prio = sWaiting->IRQgetPriority();
+ if(prio > Thread::IRQgetCurrentThread()->IRQgetPriority())
+ Scheduler::IRQfindNextThread();
+ sWaiting = 0;
+ }
+ }
+
+ DMA2->LIFCR |= DMA_LIFCR_CTEIF2 // Clear transfer error flag (not handled)
+ | DMA_LIFCR_CHTIF2 // Clear half transfer flag
+ | DMA_LIFCR_CTCIF2; // Clear transfer completed flag
+}
+
+void __attribute__((naked)) DMA2_Stream2_IRQHandler()
+{
+ saveContext();
+ asm volatile("bl _Z14DmaHandlerImplv");
+ restoreContext();
+}
+
+
+streamId inputStream_start(const enum AudioSource source,
+ const enum AudioPriority prio,
+ stream_sample_t * const buf,
+ const size_t bufLength,
+ const enum BufMode mode,
+ const uint32_t sampleRate)
+{
+ (void) prio; // TODO: input stream does not have priority
+
+ // Check if buffer is in CCM area or not, since DMA cannot access CCM RAM
+ if(reinterpret_cast< uint32_t >(buf) < 0x20000000) return -1;
+
+ /*
+ * Critical section for inUse flag management, makes the code below
+ * thread-safe.
+ */
+ {
+ FastInterruptDisableLock dLock;
+ if(inUse) return -1;
+ inUse = true;
+ }
+
+ bufMode = mode;
+ bufAddr = reinterpret_cast< uint32_t >(buf);
+ bufLen = bufLength;
+
+ RCC->APB2ENR |= RCC_APB2ENR_ADC2EN; // Enable ADC
+ RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // Enable conv. timebase timer
+ RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN; // Enable DMA
+ __DSB();
+
+ /*
+ * TIM2 for conversion triggering via TIM2_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.
+ */
+ TIM2->PSC = 83;
+ TIM2->ARR = (1000000/sampleRate) - 1;
+ TIM2->CNT = 0;
+ TIM2->EGR = TIM_EGR_UG;
+ TIM2->CR2 = TIM_CR2_MMS_1;
+ TIM2->CR1 = TIM_CR1_CEN;
+
+ /* DMA2 Stream 2 common configuration:
+ * - channel 1: ADC2
+ * - high priority
+ * - half-word transfer, both memory and peripheral
+ * - increment memory
+ * - peripheral-to-memory transfer
+ */
+ DMA2_Stream2->PAR = reinterpret_cast< uint32_t >(&(ADC2->DR));
+ DMA2_Stream2->M0AR = reinterpret_cast< uint32_t >(buf);
+ DMA2_Stream2->NDTR = bufLength;
+ DMA2_Stream2->CR = DMA_SxCR_CHSEL_0 // Channel 1
+ | DMA_SxCR_MSIZE_0 // Memory size: 16 bit
+ | DMA_SxCR_PSIZE_0 // Peripheral size: 16 bit
+ | DMA_SxCR_PL_1 // High priority
+ | DMA_SxCR_MINC; // Increment memory
+
+ /*
+ * Configure DMA and memory pointers according to buffer management mode.
+ * In linear and circular mode all the buffer is returned, in double circular
+ * buffer mode the buffer pointer is managed inside the DMA ISR.
+ */
+ switch(mode)
+ {
+ case BUF_LINEAR:
+ DMA2_Stream2->CR |= DMA_SxCR_TCIE; // Interrupt on transfer end
+ bufCurr = bufAddr; // Return all the buffer
+ break;
+
+ case BUF_CIRC:
+ DMA2_Stream2->CR |= DMA_SxCR_CIRC // Circular mode
+ | DMA_SxCR_TCIE; // Interrupt on transfer end
+ bufCurr = bufAddr; // Return all the buffer
+ break;
+
+ case BUF_CIRC_DOUBLE:
+ DMA2_Stream2->CR |= DMA_SxCR_CIRC // Circular mode
+ | DMA_SxCR_HTIE // Interrupt on half transfer
+ | DMA_SxCR_TCIE; // Interrupt on transfer end
+ break;
+
+ default:
+ inUse = false; // Invalid setting, release flag and return error.
+ return -1;
+ break;
+ }
+
+ // Configure NVIC interrupt
+ NVIC_ClearPendingIRQ(DMA2_Stream2_IRQn);
+ NVIC_SetPriority(DMA2_Stream2_IRQn, 10);
+ NVIC_EnableIRQ(DMA2_Stream2_IRQn);
+
+ /*
+ * ADC2 configuration.
+ *
+ * ADC clock is APB2 frequency divided by 8, giving 10.5MHz.
+ * Channel sample time set to 144 cycles, total conversion time 156 cycles.
+ * Convert one channel only, no overrun interrupt, 12-bit resolution,
+ * no analog watchdog, discontinuous mode, no end of conversion interrupts.
+ */
+ ADC->CCR |= ADC_CCR_ADCPRE;
+ ADC2->SMPR2 = ADC_SMPR2_SMP2
+ | ADC_SMPR2_SMP1;
+ ADC2->SQR1 = 0; // Convert one channel
+ ADC2->CR1 |= ADC_CR1_DISCEN;
+ ADC2->CR2 |= ADC_CR2_EXTEN_0 // Trigger on rising edge
+ | ADC_CR2_EXTSEL_1
+ | ADC_CR2_EXTSEL_2 // 0b0110 TIM2_TRGO trig. source
+ | ADC_CR2_DDS // Enable DMA data transfer
+ | ADC_CR2_DMA;
+
+ /*
+ * Select ADC channel according to signal source:
+ * - CH3, mic input on PA3 (vox level)
+ * - CH13, audio from RTX on PC13
+ */
+ switch(source)
+ {
+ case SOURCE_MIC:
+ gpio_setMode(GPIOA, 3, INPUT_ANALOG);
+ ADC2->SQR3 = 3;
+ break;
+
+ case SOURCE_RTX:
+ gpio_setMode(GPIOC, 13, INPUT_ANALOG);
+ ADC2->SQR3 = 13;
+ break;
+
+ default:
+ inUse = false; // Unsupported source, release flag and return error.
+ return -1;
+ break;
+ }
+
+ if((mode == BUF_CIRC) || (mode == BUF_CIRC_DOUBLE))
+ {
+ DMA2_Stream2->CR |= DMA_SxCR_EN; // Enable DMA
+ ADC2->CR2 |= ADC_CR2_ADON; // Enable ADC
+ }
+
+ return 0;
+}
+
+dataBlock_t inputStream_getData(streamId id)
+{
+ (void) id;
+
+ if(bufMode == BUF_LINEAR)
+ {
+ // Reload DMA configuration then start DMA and ADC, stopped in ISR
+ DMA2_Stream2->PAR = reinterpret_cast< uint32_t >(&(ADC2->DR));
+ DMA2_Stream2->M0AR = bufAddr;
+ DMA2_Stream2->NDTR = bufLen;
+ DMA2_Stream2->CR |= DMA_SxCR_EN;
+ ADC2->CR2 |= ADC_CR2_ADON;
+ }
+
+ /*
+ * Put the calling thread in waiting status until data is ready.
+ */
+ {
+ FastInterruptDisableLock dLock;
+ sWaiting = Thread::IRQgetCurrentThread();
+ do
+ {
+ Thread::IRQwait();
+ {
+ FastInterruptEnableLock eLock(dLock);
+ Thread::yield();
+ }
+
+ }while(sWaiting);
+ }
+
+ dataBlock_t block;
+ block.data = reinterpret_cast< stream_sample_t *>(bufCurr);
+ block.len = bufLen;
+ if(bufMode == BUF_CIRC_DOUBLE) block.len /= 2;
+
+ return block;
+}
+
+void inputStream_stop(streamId id)
+{
+ (void) id;
+
+ RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN; // Disable ADC
+ RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN; // Disable conv. timebase timer
+ RCC->AHB1ENR &= ~RCC_AHB1ENR_DMA2EN; // Disable DMA
+ __DSB();
+
+ // Critical section, release inUse flag
+ FastInterruptDisableLock dLock;
+ inUse = false;
+}