kopia lustrzana https://github.com/OpenRTX/OpenRTX
Extended output stream API to support also circular double-buffered mode, implemented output stream driver for MDx
rodzic
034f5d9ee9
commit
0df1dc4f7e
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 <http://www.gnu.org/licenses/> *
|
||||
***************************************************************************/
|
||||
|
||||
#include <kernel/scheduler/scheduler.h>
|
||||
#include <interfaces/audio_stream.h>
|
||||
#include <toneGenerator_MDx.h>
|
||||
#include <data_conversion.h>
|
||||
#include <miosix.h>
|
||||
|
||||
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();
|
||||
}
|
Ładowanie…
Reference in New Issue