kopia lustrzana https://github.com/OpenRTX/OpenRTX
Removed sources of old audio stream drivers, updated stub audio driver
rodzic
cd936ea216
commit
8dc1cba1f7
|
@ -1,55 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2021 - 2023 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 <audio_stream.h>
|
|
||||||
#include <hwconfig.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
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) source;
|
|
||||||
(void) prio;
|
|
||||||
(void) buf;
|
|
||||||
(void) bufLength;
|
|
||||||
(void) mode;
|
|
||||||
(void) sampleRate;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataBlock_t inputStream_getData(streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
|
|
||||||
dataBlock_t block;
|
|
||||||
block.data = NULL;
|
|
||||||
block.len = 0;
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
void inputStream_stop(streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
}
|
|
|
@ -1,292 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2021 - 2023 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 <audio_stream.h>
|
|
||||||
#include <toneGenerator_MDx.h>
|
|
||||||
#include <peripherals/gpio.h>
|
|
||||||
#include <hwconfig.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <miosix.h>
|
|
||||||
#include <timers.h>
|
|
||||||
|
|
||||||
using namespace miosix;
|
|
||||||
|
|
||||||
|
|
||||||
static bool inUse = false; // Flag to determine if the input stream is already open.
|
|
||||||
static Thread *sWaiting = 0; // Thread waiting on interrupt.
|
|
||||||
static stream_sample_t *bufAddr = 0; // Start address of data buffer, fixed.
|
|
||||||
static stream_sample_t *bufCurr = 0; // Buffer address to be returned to application.
|
|
||||||
static size_t bufLen = 0; // Buffer length.
|
|
||||||
static 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 = 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.
|
|
||||||
*/
|
|
||||||
tim_setUpdateFreqency(TIM2, sampleRate, 84000000);
|
|
||||||
|
|
||||||
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_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 4, giving 21MHz.
|
|
||||||
* Channel sample time set to 84 cycles, total conversion time is 100
|
|
||||||
* cycles: this leads to a maximum sampling frequency of 210kHz.
|
|
||||||
* 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_0;
|
|
||||||
ADC2->SMPR2 = ADC_SMPR2_SMP2_2
|
|
||||||
| ADC_SMPR2_SMP1_2;
|
|
||||||
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, 3, INPUT_ANALOG);
|
|
||||||
ADC2->SQR3 = 13;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
inUse = false; // Unsupported source, release flag and return error.
|
|
||||||
return -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(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)
|
|
||||||
{
|
|
||||||
dataBlock_t block;
|
|
||||||
block.data = NULL;
|
|
||||||
block.len = 0;
|
|
||||||
|
|
||||||
// Invalid stream ID, return an empty data block
|
|
||||||
if(id < 0) return block;
|
|
||||||
|
|
||||||
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 = reinterpret_cast< uint32_t >(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
block.data = bufCurr;
|
|
||||||
block.len = bufLen;
|
|
||||||
if(bufMode == BUF_CIRC_DOUBLE) block.len /= 2;
|
|
||||||
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
void inputStream_stop(streamId id)
|
|
||||||
{
|
|
||||||
if(id < 0) return;
|
|
||||||
|
|
||||||
TIM2->CR1 &= ~TIM_CR1_CEN; // Shut down timebase
|
|
||||||
ADC2->CR2 &= ~ADC_CR2_ADON; // Shut down ADC
|
|
||||||
DMA2_Stream2->CR &= ~DMA_SxCR_EN; // Shut down DMA transfer
|
|
||||||
|
|
||||||
RCC->APB2ENR &= ~RCC_APB2ENR_ADC2EN; // Disable ADC
|
|
||||||
RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN; // Disable conv. timebase timer
|
|
||||||
__DSB();
|
|
||||||
|
|
||||||
// Critical section: release inUse flag, invalidate (partial) data, wake up
|
|
||||||
// thread.
|
|
||||||
{
|
|
||||||
FastInterruptDisableLock dLock;
|
|
||||||
inUse = false;
|
|
||||||
bufCurr = 0;
|
|
||||||
bufLen = 0;
|
|
||||||
if(sWaiting != 0) sWaiting->IRQwakeup();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,290 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2021 - 2023 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 <audio_stream.h>
|
|
||||||
#include <toneGenerator_MDx.h>
|
|
||||||
#include <peripherals/gpio.h>
|
|
||||||
#include <hwconfig.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <miosix.h>
|
|
||||||
#include <timers.h>
|
|
||||||
|
|
||||||
using namespace miosix;
|
|
||||||
|
|
||||||
|
|
||||||
static bool inUse = false; // Flag to determine if the input stream is already open.
|
|
||||||
static Thread *sWaiting = 0; // Thread waiting on interrupt.
|
|
||||||
static stream_sample_t *bufAddr = 0; // Start address of data buffer, fixed.
|
|
||||||
static stream_sample_t *bufCurr = 0; // Buffer address to be returned to application.
|
|
||||||
static size_t bufLen = 0; // Buffer length.
|
|
||||||
static 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 = 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.
|
|
||||||
*/
|
|
||||||
tim_setUpdateFreqency(TIM2, sampleRate, 84000000);
|
|
||||||
|
|
||||||
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_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 4, giving 21MHz.
|
|
||||||
* Channel sample time set to 84 cycles, total conversion time is 100
|
|
||||||
* cycles: this leads to a maximum sampling frequency of 210kHz.
|
|
||||||
* 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_0;
|
|
||||||
ADC2->SMPR2 = ADC_SMPR2_SMP2_2
|
|
||||||
| ADC_SMPR2_SMP1_2;
|
|
||||||
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:
|
|
||||||
* - CH1, audio from RTX on PA1
|
|
||||||
* - CH2, audio from microphone on PA2
|
|
||||||
*/
|
|
||||||
switch(source)
|
|
||||||
{
|
|
||||||
case SOURCE_MIC:
|
|
||||||
gpio_setMode(AUDIO_MIC, INPUT_ANALOG);
|
|
||||||
ADC2->SQR3 = 2;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SOURCE_RTX:
|
|
||||||
gpio_setMode(BASEBAND_RX, INPUT_ANALOG);
|
|
||||||
ADC2->SQR3 = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
inUse = false; // Unsupported source, release flag and return error.
|
|
||||||
return -1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(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)
|
|
||||||
{
|
|
||||||
dataBlock_t block;
|
|
||||||
block.data = NULL;
|
|
||||||
block.len = 0;
|
|
||||||
|
|
||||||
// Invalid stream ID, return an empty data block
|
|
||||||
if(id < 0) return block;
|
|
||||||
|
|
||||||
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 = reinterpret_cast< uint32_t >(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 != 0) && (inUse == true));
|
|
||||||
}
|
|
||||||
|
|
||||||
block.data = bufCurr;
|
|
||||||
block.len = bufLen;
|
|
||||||
if(bufMode == BUF_CIRC_DOUBLE) block.len /= 2;
|
|
||||||
|
|
||||||
return block;
|
|
||||||
}
|
|
||||||
|
|
||||||
void inputStream_stop(streamId id)
|
|
||||||
{
|
|
||||||
if(id < 0) return;
|
|
||||||
|
|
||||||
TIM2->CR1 = 0; // Shut down timebase
|
|
||||||
ADC2->CR2 = 0; // Shut down ADC
|
|
||||||
DMA2_Stream2->CR = 0; // Shut down DMA transfer
|
|
||||||
|
|
||||||
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 and invalidate (partial) data.
|
|
||||||
// Releasing the "inUse" flag cause the wake up of pending threads.
|
|
||||||
FastInterruptDisableLock dLock;
|
|
||||||
bufCurr = 0;
|
|
||||||
bufLen = 0;
|
|
||||||
inUse = false;
|
|
||||||
}
|
|
|
@ -1,366 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2022 - 2023 by Alain Carlucci *
|
|
||||||
* *
|
|
||||||
* 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 <hwconfig.h>
|
|
||||||
#include <audio_stream.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <cassert>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
streamId gNextAvailableStreamId = 0;
|
|
||||||
|
|
||||||
class InputStream
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
InputStream(enum AudioSource source,
|
|
||||||
enum AudioPriority priority,
|
|
||||||
stream_sample_t* buf,
|
|
||||||
size_t bufLength,
|
|
||||||
enum BufMode mode,
|
|
||||||
uint32_t sampleRate)
|
|
||||||
: m_run_thread(true), m_func_running(false)
|
|
||||||
{
|
|
||||||
if (bufLength % 2)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "InputStream error: invalid bufLength %lu\n",
|
|
||||||
bufLength);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_db_ready[0] = m_db_ready[1] = false;
|
|
||||||
|
|
||||||
std::string sourceString;
|
|
||||||
switch (source)
|
|
||||||
{
|
|
||||||
case SOURCE_MIC:
|
|
||||||
sourceString = "MIC";
|
|
||||||
break;
|
|
||||||
case SOURCE_MCU:
|
|
||||||
sourceString = "MCU";
|
|
||||||
break;
|
|
||||||
case SOURCE_RTX:
|
|
||||||
sourceString = "RTX";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_fp = fopen((sourceString + ".raw").c_str(), "rb");
|
|
||||||
if (!m_fp)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "InputStream error: cannot open: %s.raw\n",
|
|
||||||
sourceString.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fseek(m_fp, 0, SEEK_END);
|
|
||||||
m_size = ftell(m_fp);
|
|
||||||
fseek(m_fp, 0, SEEK_SET);
|
|
||||||
if (m_size % 2 || m_size == 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "InputStream error: invalid file: %s.raw\n",
|
|
||||||
sourceString.c_str());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_valid = true;
|
|
||||||
|
|
||||||
changeId();
|
|
||||||
setStreamData(priority, buf, bufLength, mode, sampleRate);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValid() const
|
|
||||||
{
|
|
||||||
return m_valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
~InputStream()
|
|
||||||
{
|
|
||||||
stopThread();
|
|
||||||
|
|
||||||
if (m_fp) fclose(m_fp);
|
|
||||||
}
|
|
||||||
|
|
||||||
dataBlock_t getDataBlock()
|
|
||||||
{
|
|
||||||
if (!m_valid) return {nullptr, 0};
|
|
||||||
|
|
||||||
switch (m_mode)
|
|
||||||
{
|
|
||||||
case BufMode::BUF_LINEAR:
|
|
||||||
{
|
|
||||||
// With this mode, just sleep for the right amount of time
|
|
||||||
// and return the buffer content
|
|
||||||
if (!fillBuffer(m_buf, m_bufLength)) return {NULL, 0};
|
|
||||||
|
|
||||||
return {m_buf, m_bufLength};
|
|
||||||
}
|
|
||||||
case BufMode::BUF_CIRC_DOUBLE:
|
|
||||||
{
|
|
||||||
// If this mode is selected, wait for the readiness of the
|
|
||||||
// current slice and return it
|
|
||||||
|
|
||||||
int id = m_db_curread;
|
|
||||||
|
|
||||||
// Wait for `m_buf` to be ready
|
|
||||||
while (!m_db_ready[id] && m_run_thread)
|
|
||||||
{
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
if (!m_run_thread) return {NULL, 0};
|
|
||||||
|
|
||||||
// Return the buffer contents
|
|
||||||
auto* pos = m_buf + id * (m_bufLength / 2);
|
|
||||||
m_db_ready[id] = 0;
|
|
||||||
|
|
||||||
// Update the read buffer
|
|
||||||
m_db_curread = (id + 1) % 2;
|
|
||||||
return {pos, m_bufLength / 2};
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return {NULL, 0};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AudioPriority priority() const
|
|
||||||
{
|
|
||||||
return m_prio;
|
|
||||||
}
|
|
||||||
|
|
||||||
streamId id() const
|
|
||||||
{
|
|
||||||
return m_id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void changeId()
|
|
||||||
{
|
|
||||||
m_id = gNextAvailableStreamId;
|
|
||||||
gNextAvailableStreamId += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setStreamData(AudioPriority priority,
|
|
||||||
stream_sample_t* buf,
|
|
||||||
size_t bufLength,
|
|
||||||
BufMode mode,
|
|
||||||
uint32_t sampleRate)
|
|
||||||
{
|
|
||||||
if (!m_valid) return;
|
|
||||||
|
|
||||||
stopThread();
|
|
||||||
m_run_thread = true; // set it as runnable again
|
|
||||||
|
|
||||||
// HERE stop thread
|
|
||||||
m_prio = priority;
|
|
||||||
m_buf = buf;
|
|
||||||
m_bufLength = bufLength;
|
|
||||||
m_mode = mode;
|
|
||||||
m_sampleRate = sampleRate;
|
|
||||||
|
|
||||||
switch (m_mode)
|
|
||||||
{
|
|
||||||
case BufMode::BUF_LINEAR:
|
|
||||||
// TODO: stop a running thread
|
|
||||||
break;
|
|
||||||
case BufMode::BUF_CIRC_DOUBLE:
|
|
||||||
m_thread =
|
|
||||||
std::thread(std::bind(&InputStream::threadFunc, this));
|
|
||||||
// TODO: start thread
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool m_valid = false;
|
|
||||||
FILE* m_fp = nullptr;
|
|
||||||
uint64_t m_size = 0;
|
|
||||||
|
|
||||||
streamId m_id;
|
|
||||||
AudioPriority m_prio;
|
|
||||||
BufMode m_mode;
|
|
||||||
uint32_t m_sampleRate = 0;
|
|
||||||
|
|
||||||
stream_sample_t* m_buf = nullptr;
|
|
||||||
size_t m_bufLength = 0;
|
|
||||||
|
|
||||||
size_t m_db_curwrite = 0;
|
|
||||||
size_t m_db_curread = 0;
|
|
||||||
std::atomic<bool> m_db_ready[2];
|
|
||||||
std::atomic<bool> m_run_thread;
|
|
||||||
std::atomic<bool> m_func_running;
|
|
||||||
std::thread m_thread;
|
|
||||||
|
|
||||||
// Emulate an ADC that reads to the circular buffer
|
|
||||||
void threadFunc()
|
|
||||||
{
|
|
||||||
m_db_ready[0] = m_db_ready[1] = false;
|
|
||||||
while (m_run_thread)
|
|
||||||
{
|
|
||||||
m_db_ready[0] = false;
|
|
||||||
m_db_curwrite = 0;
|
|
||||||
fillBuffer(m_buf, m_bufLength / 2);
|
|
||||||
m_db_ready[0] = true;
|
|
||||||
if (!m_run_thread) break;
|
|
||||||
|
|
||||||
m_db_curwrite = 1;
|
|
||||||
m_db_ready[1] = false;
|
|
||||||
fillBuffer(m_buf + m_bufLength / 2, m_bufLength / 2);
|
|
||||||
m_db_ready[1] = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a blocking function that emulates an ADC writing to the
|
|
||||||
// specified memory region. It takes the same time that an ADC would take
|
|
||||||
// to sample the same quantity of data.
|
|
||||||
bool fillBuffer(stream_sample_t* dest, size_t sz)
|
|
||||||
{
|
|
||||||
size_t i = 0;
|
|
||||||
if (!m_run_thread) return false;
|
|
||||||
|
|
||||||
assert(m_func_running == false);
|
|
||||||
m_func_running = true;
|
|
||||||
|
|
||||||
auto reset_func_running = [&]()
|
|
||||||
{
|
|
||||||
assert(m_func_running == true);
|
|
||||||
m_func_running = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
using std::chrono::microseconds;
|
|
||||||
|
|
||||||
if (m_sampleRate > 0)
|
|
||||||
{
|
|
||||||
// Do a piecewise-sleep so that it's easily interruptible
|
|
||||||
uint64_t microsec = sz * 1000000 / m_sampleRate;
|
|
||||||
while (microsec > 10000)
|
|
||||||
{
|
|
||||||
if (!m_run_thread)
|
|
||||||
{
|
|
||||||
// Early exit if the class is being deallocated
|
|
||||||
reset_func_running();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::this_thread::sleep_for(microseconds(10000));
|
|
||||||
microsec -= 10000;
|
|
||||||
}
|
|
||||||
std::this_thread::sleep_for(microseconds(microsec));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!m_run_thread)
|
|
||||||
{
|
|
||||||
// Early exit if the class is being deallocated
|
|
||||||
reset_func_running();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill the buffer
|
|
||||||
while (i < sz)
|
|
||||||
{
|
|
||||||
auto n = fread(dest + i, 2, sz - i, m_fp);
|
|
||||||
if (n < (sz - i)) fseek(m_fp, 0, SEEK_SET);
|
|
||||||
i += n;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(i == sz);
|
|
||||||
reset_func_running();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void stopThread()
|
|
||||||
{
|
|
||||||
m_run_thread = false;
|
|
||||||
|
|
||||||
while (m_func_running)
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
|
|
||||||
if (m_thread.joinable()) m_thread.join();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<AudioSource, std::unique_ptr<InputStream>> gOpenStreams;
|
|
||||||
|
|
||||||
streamId inputStream_start(const enum AudioSource source,
|
|
||||||
const enum AudioPriority priority,
|
|
||||||
stream_sample_t* const buf,
|
|
||||||
const size_t bufLength,
|
|
||||||
const enum BufMode mode,
|
|
||||||
const uint32_t sampleRate)
|
|
||||||
{
|
|
||||||
auto it = gOpenStreams.find(source);
|
|
||||||
if (it != gOpenStreams.end())
|
|
||||||
{
|
|
||||||
auto& inputStream = it->second;
|
|
||||||
if (inputStream->priority() >= priority) return -1;
|
|
||||||
|
|
||||||
inputStream->changeId();
|
|
||||||
inputStream->setStreamData(priority, buf, bufLength, mode, sampleRate);
|
|
||||||
|
|
||||||
return inputStream->id();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto stream = std::make_unique<InputStream>(source, priority, buf,
|
|
||||||
bufLength, mode, sampleRate);
|
|
||||||
|
|
||||||
if (!stream->isValid()) return -1;
|
|
||||||
|
|
||||||
const auto id = stream->id();
|
|
||||||
|
|
||||||
// New stream, move it into the map
|
|
||||||
gOpenStreams[source] = std::move(stream);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
dataBlock_t inputStream_getData(streamId id)
|
|
||||||
{
|
|
||||||
InputStream* stream = nullptr;
|
|
||||||
for (auto& i : gOpenStreams)
|
|
||||||
if (i.second->id() == id)
|
|
||||||
{
|
|
||||||
stream = i.second.get();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream == nullptr) return dataBlock_t{NULL, 0};
|
|
||||||
|
|
||||||
return stream->getDataBlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
void inputStream_stop(streamId id)
|
|
||||||
{
|
|
||||||
AudioSource src;
|
|
||||||
bool found = false;
|
|
||||||
for (auto& i : gOpenStreams)
|
|
||||||
if (i.second->id() == id)
|
|
||||||
{
|
|
||||||
found = true;
|
|
||||||
src = i.first;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) return;
|
|
||||||
|
|
||||||
gOpenStreams.erase(src);
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2021 - 2023 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 <audio_stream.h>
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
(void) destination;
|
|
||||||
(void) prio;
|
|
||||||
(void) buf;
|
|
||||||
(void) length;
|
|
||||||
(void) mode;
|
|
||||||
(void) sampleRate;
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream_sample_t *outputStream_getIdleBuffer(const streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool outputStream_sync(const streamId id, const bool bufChanged)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
(void) bufChanged;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void outputStream_stop(const streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
}
|
|
||||||
|
|
||||||
void outputStream_terminate(const streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
}
|
|
|
@ -1,253 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2022 - 2023 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 <audio_stream.h>
|
|
||||||
#include <toneGenerator_MDx.h>
|
|
||||||
#include <data_conversion.h>
|
|
||||||
#include <timers.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 and restore priority level
|
|
||||||
running = false;
|
|
||||||
reqFinish = false;
|
|
||||||
circularMode = false;
|
|
||||||
priority = PRIO_BEEP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \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 or pending termination request.
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
tim_setUpdateFreqency(TIM7, sampleRate, 84000000);
|
|
||||||
TIM7->CNT = 0;
|
|
||||||
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 = buf[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();
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2021 - 2023 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 <audio_stream.h>
|
|
||||||
#include <peripherals/gpio.h>
|
|
||||||
#include <data_conversion.h>
|
|
||||||
#include <hwconfig.h>
|
|
||||||
#include <timers.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()
|
|
||||||
{
|
|
||||||
// Stop DMA transfers
|
|
||||||
DMA1_Stream5->CR = 0;
|
|
||||||
DMA1_Stream6->CR = 0;
|
|
||||||
|
|
||||||
TIM7->CR1 = 0; // Shutdown timer
|
|
||||||
DAC->SR = 0; // Clear status flags
|
|
||||||
DAC->CR = DAC_CR_EN1; // Keep only channel 1 active
|
|
||||||
DAC->DHR12R1 = 1365; // Set channel 1 (RTX) to about 1.1V when idle
|
|
||||||
|
|
||||||
// Clear flags and restore priority level
|
|
||||||
running = false;
|
|
||||||
reqFinish = false;
|
|
||||||
circularMode = false;
|
|
||||||
priority = PRIO_BEEP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* \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 and 6
|
|
||||||
uint32_t mask = DMA_HISR_TEIF5
|
|
||||||
| DMA_HISR_TCIF5
|
|
||||||
| DMA_HISR_HTIF5
|
|
||||||
| DMA_HISR_TEIF6
|
|
||||||
| DMA_HISR_TCIF6
|
|
||||||
| DMA_HISR_HTIF6;
|
|
||||||
|
|
||||||
DMA1->HIFCR = DMA1->HISR & mask;
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
* APB1 frequency is 42MHz but timer runs at 84MHz, tick rate is 1MHz,
|
|
||||||
* reload register is configured based on desired sample rate.
|
|
||||||
*/
|
|
||||||
tim_setUpdateFreqency(TIM7, sampleRate, 84000000);
|
|
||||||
TIM7->CNT = 0;
|
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,272 +0,0 @@
|
||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2021 - 2023 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 <audio_stream.h>
|
|
||||||
#include <pulse/pulseaudio.h>
|
|
||||||
#include <pulse/simple.h>
|
|
||||||
#include <pulse/error.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
// Expand opaque pa_simple struct
|
|
||||||
struct pa_simple
|
|
||||||
{
|
|
||||||
pa_threaded_mainloop *mainloop;
|
|
||||||
pa_context *context;
|
|
||||||
pa_stream *stream;
|
|
||||||
pa_stream_direction_t direction;
|
|
||||||
const void *read_data;
|
|
||||||
size_t read_index;
|
|
||||||
size_t read_length;
|
|
||||||
int operation_success;
|
|
||||||
};
|
|
||||||
|
|
||||||
static enum BufMode bufMode; // Buffer operation mode
|
|
||||||
static enum AudioPriority priority = PRIO_BEEP; // Priority level
|
|
||||||
static bool running = false; // Stream is running
|
|
||||||
static size_t bufLen = 0; // Total buffer length
|
|
||||||
static stream_sample_t *playBuf = NULL; // Buffer being reproduced
|
|
||||||
static stream_sample_t *idleBuf = NULL; // Idle buffer available to be filled
|
|
||||||
static pa_simple *paInstance = NULL; // Pulseaudio instance
|
|
||||||
static size_t remaining = 0;
|
|
||||||
static pthread_cond_t barrier;
|
|
||||||
static pthread_mutex_t mutex;
|
|
||||||
|
|
||||||
static void buf_circ_write_cb(pa_stream* s, size_t length, void* userdata)
|
|
||||||
{
|
|
||||||
(void) userdata;
|
|
||||||
|
|
||||||
if((s == NULL) || (length <= 0))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(length > remaining)
|
|
||||||
{
|
|
||||||
// We can play all the rest of the buffer
|
|
||||||
pa_stream_write(s, playBuf, remaining * sizeof(stream_sample_t),
|
|
||||||
NULL, 0, PA_SEEK_RELATIVE);
|
|
||||||
remaining = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pa_stream_write(s, playBuf, length, NULL, 0, PA_SEEK_RELATIVE);
|
|
||||||
|
|
||||||
if (remaining > length)
|
|
||||||
remaining -= length;
|
|
||||||
else
|
|
||||||
remaining = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// All data in playBuffer has been sent
|
|
||||||
if(remaining == 0)
|
|
||||||
{
|
|
||||||
// Reload counter
|
|
||||||
remaining = bufLen/2;
|
|
||||||
|
|
||||||
pthread_mutex_lock(&mutex);
|
|
||||||
|
|
||||||
// Swap idle and play buffers
|
|
||||||
stream_sample_t *tmp = idleBuf;
|
|
||||||
playBuf = idleBuf;
|
|
||||||
idleBuf = tmp;
|
|
||||||
|
|
||||||
// Unlock waiting threads
|
|
||||||
pthread_cond_signal(&barrier);
|
|
||||||
pthread_mutex_unlock(&mutex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
streamId outputStream_start(const enum AudioSink destination,
|
|
||||||
const enum AudioPriority prio,
|
|
||||||
stream_sample_t* const buffer,
|
|
||||||
const size_t length,
|
|
||||||
const enum BufMode mode,
|
|
||||||
const uint32_t sampleRate)
|
|
||||||
{
|
|
||||||
|
|
||||||
if(destination != SINK_SPK)
|
|
||||||
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) outputStream_stop(0); // Higher priority, takes over.
|
|
||||||
while(running) ; // Same priority, wait.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign priority and set stream as running
|
|
||||||
running = true;
|
|
||||||
priority = prio;
|
|
||||||
bufMode = mode;
|
|
||||||
playBuf = buffer;
|
|
||||||
idleBuf = buffer + (length/2);
|
|
||||||
bufLen = length;
|
|
||||||
remaining = length/2;
|
|
||||||
|
|
||||||
int paError = 0;
|
|
||||||
bool success = true;
|
|
||||||
|
|
||||||
if(paInstance == NULL)
|
|
||||||
{
|
|
||||||
// Stream data sample format
|
|
||||||
static pa_sample_spec spec;
|
|
||||||
spec.format = PA_SAMPLE_S16LE;
|
|
||||||
spec.rate = 0;
|
|
||||||
spec.channels = 1;
|
|
||||||
spec.rate = sampleRate;
|
|
||||||
|
|
||||||
paInstance = pa_simple_new(NULL, "OpenRTX", PA_STREAM_PLAYBACK, NULL,
|
|
||||||
"Audio out", &spec, NULL, NULL, &paError);
|
|
||||||
|
|
||||||
if(paInstance == NULL)
|
|
||||||
{
|
|
||||||
fprintf(stderr, __FILE__ ": pa_simple_new() failed: %s\n",
|
|
||||||
pa_strerror(paError));
|
|
||||||
|
|
||||||
success = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
pthread_mutex_init(&mutex, NULL);
|
|
||||||
pthread_cond_init(&barrier, NULL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch(mode)
|
|
||||||
{
|
|
||||||
case BUF_LINEAR:
|
|
||||||
if(pa_simple_write(paInstance, buffer, length, &paError) < 0)
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case BUF_CIRC_DOUBLE:
|
|
||||||
{
|
|
||||||
if(paInstance->stream == NULL)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register write callback
|
|
||||||
pa_stream_set_write_callback(paInstance->stream, buf_circ_write_cb,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
// Set minimal prebuffering
|
|
||||||
const pa_buffer_attr attr =
|
|
||||||
{
|
|
||||||
.fragsize = -1,
|
|
||||||
.maxlength = -1,
|
|
||||||
.minreq = -1,
|
|
||||||
.prebuf = 320,
|
|
||||||
.tlength = -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
pa_stream_set_buffer_attr(paInstance->stream, &attr, NULL, NULL);
|
|
||||||
|
|
||||||
// Get maximum pulse buffer size
|
|
||||||
size_t wsize = pa_stream_writable_size(paInstance->stream);
|
|
||||||
if(wsize > (length / 2))
|
|
||||||
wsize = length / 2;
|
|
||||||
|
|
||||||
// Start writing loop
|
|
||||||
pa_stream_write(paInstance->stream, playBuf, wsize, NULL, 0,
|
|
||||||
PA_SEEK_RELATIVE);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(success == false)
|
|
||||||
{
|
|
||||||
running = false;
|
|
||||||
priority = PRIO_BEEP;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
stream_sample_t *outputStream_getIdleBuffer(const streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
|
|
||||||
stream_sample_t *ptr = NULL;
|
|
||||||
|
|
||||||
if(bufMode == BUF_CIRC_DOUBLE)
|
|
||||||
{
|
|
||||||
pthread_mutex_lock(&mutex);
|
|
||||||
ptr = idleBuf;
|
|
||||||
pthread_mutex_unlock(&mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool outputStream_sync(const streamId id, const bool bufChanged)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
(void) bufChanged;
|
|
||||||
|
|
||||||
if(bufMode == BUF_CIRC_DOUBLE)
|
|
||||||
{
|
|
||||||
pthread_mutex_lock(&mutex);
|
|
||||||
pthread_cond_wait(&barrier, &mutex);
|
|
||||||
pthread_mutex_unlock(&mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// TODO syncronisation barrrier also for linear buffer mode
|
|
||||||
//
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void outputStream_stop(const streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
|
|
||||||
int error = 0;
|
|
||||||
if (pa_simple_flush(paInstance, &error) < 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n",
|
|
||||||
pa_strerror(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
running = false;
|
|
||||||
priority = PRIO_BEEP;
|
|
||||||
}
|
|
||||||
|
|
||||||
void outputStream_terminate(const streamId id)
|
|
||||||
{
|
|
||||||
(void) id;
|
|
||||||
|
|
||||||
running = false;
|
|
||||||
priority = PRIO_BEEP;
|
|
||||||
|
|
||||||
if(paInstance != NULL)
|
|
||||||
{
|
|
||||||
pa_simple_free(paInstance);
|
|
||||||
pthread_mutex_destroy(&mutex);
|
|
||||||
pthread_cond_destroy(&barrier);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,9 +18,21 @@
|
||||||
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
* along with this program; if not, see <http://www.gnu.org/licenses/> *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include <interfaces/audio_stream.h>
|
|
||||||
#include <interfaces/audio.h>
|
#include <interfaces/audio.h>
|
||||||
|
|
||||||
|
const struct audioDevice outputDevices[] =
|
||||||
|
{
|
||||||
|
{NULL, 0, 0, SINK_MCU},
|
||||||
|
{NULL, 0, 0, SINK_RTX},
|
||||||
|
{NULL, 0, 0, SINK_SPK},
|
||||||
|
};
|
||||||
|
|
||||||
|
const struct audioDevice inputDevices[] =
|
||||||
|
{
|
||||||
|
{NULL, 0, 0, SINK_MCU},
|
||||||
|
{NULL, 0, 0, SINK_RTX},
|
||||||
|
{NULL, 0, 0, SINK_SPK},
|
||||||
|
};
|
||||||
|
|
||||||
void audio_init()
|
void audio_init()
|
||||||
{
|
{
|
||||||
|
|
Ładowanie…
Reference in New Issue