diff --git a/meson.build b/meson.build index 3c4f0cdf..be1023f2 100644 --- a/meson.build +++ b/meson.build @@ -31,6 +31,7 @@ openrtx_src = ['openrtx/src/core/state.c', 'openrtx/src/core/dsp.cpp', 'openrtx/src/core/cps.c', 'openrtx/src/core/crc.c', + 'openrtx/src/core/data_conversion.c', 'openrtx/src/core/memory_profiling.cpp', 'openrtx/src/ui/ui.c', 'openrtx/src/ui/ui_main.c', @@ -135,6 +136,7 @@ mdx_src = ['openrtx/src/core/xmodem.c', 'platform/drivers/NVM/nvmem_settings_MDx.c', 'platform/drivers/audio/audio_MDx.c', 'platform/drivers/audio/inputStream_MDx.cpp', + 'platform/drivers/audio/outputStream_MDx.cpp', 'platform/drivers/baseband/HR_Cx000.cpp', 'platform/drivers/backlight/backlight_MDx.c', 'platform/drivers/tones/toneGenerator_MDx.cpp', diff --git a/openrtx/include/interfaces/audio_stream.h b/openrtx/include/interfaces/audio_stream.h index ec4b470f..fba36fa6 100644 --- a/openrtx/include/interfaces/audio_stream.h +++ b/openrtx/include/interfaces/audio_stream.h @@ -1,8 +1,8 @@ /*************************************************************************** - * Copyright (C) 2021 by Federico Amedeo Izzo IU2NUO, * - * Niccolò Izzo IU2KIN * - * Frederik Saraci IU2NRO * - * Silvano Seva IU2KWO * + * 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 * @@ -35,7 +35,8 @@ typedef int8_t streamId; enum BufMode { BUF_LINEAR, ///< Linear buffer mode, conversion stops when full. - BUF_CIRC_DOUBLE ///< Circular double buffer mode, conversion never stops, thread woken up whenever half of the buffer is full. + BUF_CIRC_DOUBLE ///< Circular double buffer mode, conversion never stops, + /// thread woken up whenever half of the buffer is full. }; typedef struct @@ -59,7 +60,8 @@ dataBlock_t; * @param bufLength: length of the buffer, in elements. * @param mode: buffer management mode. * @param sampleRate: sample rate, in Hz. - * @return a unique identifier for the stream or -1 if the stream could not be opened. + * @return a unique identifier for the stream or -1 if the stream could not be + * opened. */ streamId inputStream_start(const enum AudioSource source, const enum AudioPriority prio, @@ -75,15 +77,15 @@ streamId inputStream_start(const enum AudioSource source, * * @param id: identifier of the stream from which data is get. * @return dataBlock_t containing a pointer to the chunk head and its length. If - * another thread is pending on this function, it returns immediately a dataBlock_t - * cointaining < NULL, 0 >. + * another thread is pending on this function, it returns immediately a + * dataBlock_t cointaining < NULL, 0 >. */ dataBlock_t inputStream_getData(streamId id); /** * Release the current input stream, allowing for a new call of startInputStream. - * If this function is called when sampler is running, acquisition is stopped and - * any thread waiting on getData() is woken up and given a partial result. + * If this function is called when sampler is running, acquisition is stopped + * and any thread waiting on getData() is woken up and given a partial result. * * @param id: identifier of the stream to be stopped */ @@ -105,20 +107,59 @@ void inputStream_stop(streamId id); * @param buf: buffer containing the audio samples. * @param length: length of the buffer, in elements. * @param sampleRate: sample rate in Hz. - * @return a unique identifier for the stream or -1 if the stream could not be opened. + * @return a unique identifier for the stream or -1 if the stream could not be + * opened. */ 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); +/** + * Get a pointer to the section of the sample buffer not currently being read + * by the DMA peripheral. The function is to be used primarily when the output + * stream is running in double-buffered circular mode for filling a new block + * of data to the stream. + * + * @param id: stream identifier. + * @return a pointer to the idle section of the sample buffer or nullptr if the + * stream is running in linear mode. + */ +stream_sample_t *outputStream_getIdleBuffer(const streamId id); + +/** + * Synchronise with the output stream DMA transfer, blocking function. + * When the stream is running in circular mode, execution is blocked until + * either the half or the end of the buffer is reached. In linear mode execution + * is blocked until the end of the buffer is reached. + * If the stream is not running or there is another thread waiting at the + * synchronisation point, the function returns immediately. + * + * @param id: stream identifier. + * @param bufChanged: if true, notifies the strem handler that new data has + * been written to the idle section of the data buffer. This field is valid + * only in circular double buffered mode. + * @return true if execution was effectively blocked, false if stream is not + * running or there is another thread waiting at the synchronisation point. + */ +bool outputStream_sync(const streamId id, const bool bufChanged); + +/** + * Request termination of a currently ongoing output stream. + * Stream is effectively stopped only when all the remaining data are sent. + * + * @param id: identifier of the stream to be stopped. + */ +void outputStream_stop(const streamId id); + /** * Interrupt a currently ongoing output stream before its natural ending. * * @param id: identifier of the stream to be stopped. */ -void outputStream_stop(streamId id); +void outputStream_terminate(const streamId id); #ifdef __cplusplus } diff --git a/platform/drivers/audio/outputStream_MDx.cpp b/platform/drivers/audio/outputStream_MDx.cpp new file mode 100644 index 00000000..2c6543d5 --- /dev/null +++ b/platform/drivers/audio/outputStream_MDx.cpp @@ -0,0 +1,253 @@ +/*************************************************************************** + * Copyright (C) 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 + +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. + */ +static inline void stopTransfer() +{ + TIM7->CR1 = 0; // Stop TIM7 + DMA1_Stream2->CR &= ~DMA_SxCR_EN; // Stop DMA transfer + TIM3->CCER &= ~TIM_CCER_CC3E; // Turn off compare channel + RCC->APB1ENR &= ~RCC_APB1ENR_TIM7EN; // Turn off TIM7 APB clock + __DSB(); + + // Re-activate "beeps" + toneGen_unlockBeep(); + + // Finally, clear flags + running = false; + reqFinish = false; + circularMode = false; +} + +/** + * \internal + * Actual implementation of DMA1 Stream2 interrupt handler. + */ +void __attribute__((used)) DMA_Handler() +{ + if(DMA1->LISR & DMA_LISR_HTIF2) + idleBuf = bufAddr; + else + idleBuf = bufAddr + (bufLen / 2); + + // Stop transfer for linear buffer mode, pending termination request or + // DMA error. + if((circularMode == false) || (reqFinish == true)) + { + stopTransfer(); + } + + // Clear interrupt flags + DMA1->LIFCR = DMA_LIFCR_CTCIF2 + | DMA_LIFCR_CHTIF2 + | DMA_LIFCR_CTEIF2; + + // Finally, wake up eventual pending threads + if(dmaWaiting == 0) return; + dmaWaiting->IRQwakeup(); + if(dmaWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) + Scheduler::IRQfindNextThread(); + dmaWaiting = 0; +} + +void __attribute__((naked)) DMA1_Stream2_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; + toneGen_lockBeep(); + __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. + */ + S16toU8(buf, length); + bufAddr = buf; + bufLen = length; + idleBuf = bufAddr + (bufLen / 2); + + RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; + RCC->APB1ENR |= RCC_APB1ENR_TIM7EN; + __DSB(); + + /* + * Timebase for triggering of DMA transfers. + * Bus frequency for TIM7 is 84MHz. + */ + TIM7->CNT = 0; + TIM7->PSC = 0; + TIM7->ARR = 84000000/sampleRate; + TIM7->EGR = TIM_EGR_UG; + TIM7->DIER = TIM_DIER_UDE; + + /* + * DMA stream for sample transfer, destination is TIM3 CCR3 + */ + DMA1_Stream2->NDTR = length; + DMA1_Stream2->PAR = reinterpret_cast< uint32_t >(&(TIM3->CCR3)); + DMA1_Stream2->M0AR = reinterpret_cast< uint32_t >(buf); + DMA1_Stream2->CR = DMA_SxCR_CHSEL_0 // Channel 1 + | 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_DIR_0 // Memory to peripheral + | DMA_SxCR_TCIE // Transfer complete interrupt + | DMA_SxCR_TEIE; // Transfer error interrupt + + if(mode == BUF_CIRC_DOUBLE) + { + DMA1_Stream2->CR |= DMA_SxCR_CIRC // Circular buffer mode + | DMA_SxCR_HTIE; // Half transfer interrupt + circularMode = true; + } + + DMA1_Stream2->CR |= DMA_SxCR_EN; // Enable transfer + + // Enable DMA interrupts + NVIC_ClearPendingIRQ(DMA1_Stream2_IRQn); + NVIC_SetPriority(DMA1_Stream2_IRQn, 10); + NVIC_EnableIRQ(DMA1_Stream2_IRQn); + + // Enable compare channel + TIM3->CCR3 = 0; + TIM3->CCER |= TIM_CCER_CC3E; + TIM3->CR1 |= TIM_CR1_CEN; + + // Start timer for DMA transfer triggering + 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); + S16toU8(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; + + __disable_irq(); + + stopTransfer(); + + DMA1->LIFCR = DMA_LIFCR_CTCIF2 + | DMA_LIFCR_CHTIF2 + | DMA_LIFCR_CTEIF2; + + __enable_irq(); +}