OpenRTX/platform/drivers/audio/outputStream_Mod17.cpp

306 wiersze
10 KiB
C++

/***************************************************************************
* Copyright (C) 2021 - 2022 by Federico Amedeo Izzo IU2NUO, *
* Niccolò Izzo IU2KIN *
* Frederik Saraci IU2NRO *
* Silvano Seva IU2KWO *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 3 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, see <http://www.gnu.org/licenses/> *
***************************************************************************/
#include <kernel/scheduler/scheduler.h>
#include <interfaces/audio_stream.h>
#include <interfaces/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.
*/
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
running = false;
reqFinish = false;
circularMode = false;
}
/**
* \internal
* Actual implementation of DMA interrupt handler.
*/
void __attribute__((used)) DMA_Handler()
{
// Manage half transfer interrupt
if((DMA1->HISR & DMA_HISR_HTIF5) || (DMA1->HISR & DMA_HISR_HTIF6))
idleBuf = bufAddr;
else
idleBuf = bufAddr + (bufLen / 2);
// Stop transfer for linear buffer mode or pending termination request.
if((circularMode == false) || (reqFinish == true))
{
stopTransfer();
}
// Clear interrupt flags for stream 5 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;
}