/*************************************************************************** * 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 * ***************************************************************************/ #include #include #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. */ void stopTransfer() { TIM7->CR1 = 0; // Shutdown timer DAC->CR = 0; // Disable DAC channels DAC->SR = 0; // Clear status flags // Stop DMA transfers DMA1_Stream5->CR = 0; DMA1_Stream6->CR = 0; // 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; }