diff --git a/meson.build b/meson.build index be1023f2..9c505e82 100644 --- a/meson.build +++ b/meson.build @@ -337,7 +337,7 @@ mod17_src = src + stm32f405_src + ['platform/targets/Module17/platform.c', 'platform/drivers/NVM/nvmem_Mod17.c', 'platform/drivers/baseband/radio_Mod17.cpp', 'platform/drivers/audio/inputStream_Mod17.cpp', - 'platform/drivers/audio/outputStream_Mod17.c', + 'platform/drivers/audio/outputStream_Mod17.cpp', 'platform/drivers/audio/audio_Mod17.c', 'platform/drivers/baseband/MCP4551_Mod17.cpp'] diff --git a/platform/drivers/audio/outputStream_MDx.cpp b/platform/drivers/audio/outputStream_MDx.cpp index 2c6543d5..5f23f4f4 100644 --- a/platform/drivers/audio/outputStream_MDx.cpp +++ b/platform/drivers/audio/outputStream_MDx.cpp @@ -68,8 +68,7 @@ void __attribute__((used)) DMA_Handler() else idleBuf = bufAddr + (bufLen / 2); - // Stop transfer for linear buffer mode, pending termination request or - // DMA error. + // Stop transfer for linear buffer mode or pending termination request. if((circularMode == false) || (reqFinish == true)) { stopTransfer(); @@ -80,7 +79,7 @@ void __attribute__((used)) DMA_Handler() | DMA_LIFCR_CHTIF2 | DMA_LIFCR_CTEIF2; - // Finally, wake up eventual pending threads + // Finally, wake up eventual pending threads if(dmaWaiting == 0) return; dmaWaiting->IRQwakeup(); if(dmaWaiting->IRQgetPriority()>Thread::IRQgetCurrentThread()->IRQgetPriority()) diff --git a/platform/drivers/audio/outputStream_Mod17.c b/platform/drivers/audio/outputStream_Mod17.c deleted file mode 100644 index d454fe43..00000000 --- a/platform/drivers/audio/outputStream_Mod17.c +++ /dev/null @@ -1,198 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2021 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 - -int priority = PRIO_BEEP; -bool running = false; - -void stopTransfer() -{ - /* Shutdown timer */ - TIM7->CR1 &= ~TIM_CR1_CEN; - - /* Disable DAC channels and clear underrun flags */ - DAC->CR &= ~(DAC_CR_EN1 | DAC_CR_EN2); - DAC->SR |= DAC_SR_DMAUDR1 | DAC_SR_DMAUDR2; - - /* Stop DMA transfers */ - DMA1_Stream5->CR &= ~DMA_SxCR_EN; - DMA1_Stream6->CR &= ~DMA_SxCR_EN; - - running = false; -} - -/* - * DMA 1, Stream 5: data transfer for RTX sink - */ -void __attribute__((used)) DMA1_Stream5_IRQHandler() -{ - stopTransfer(); - - DMA1->HIFCR |= DMA_HIFCR_CTCIF5 - | DMA_HIFCR_CTEIF5; - - NVIC_DisableIRQ(DMA1_Stream5_IRQn); -} - -/* - * DMA 1, Stream 6: data transfer for speaker sink - */ -void __attribute__((used)) DMA1_Stream6_IRQHandler() -{ - stopTransfer(); - - DMA1->HIFCR |= DMA_HIFCR_CTCIF6 - | DMA_HIFCR_CTEIF6; - - NVIC_DisableIRQ(DMA1_Stream6_IRQn); -} - -streamId outputStream_start(const enum AudioSink destination, - const enum AudioPriority prio, - stream_sample_t * const buf, - const size_t length, - const uint32_t sampleRate) -{ - if(destination == SINK_MCU) return -1; /* This device cannot sink to buffer */ - if(running) /* Check if a stream is already running */ - { - if(prio < priority) return -1; /* Requested priority is lower than current */ - if(prio > priority) stopTransfer(); /* Stop an ongoing stream with lower priority */ - while(running) ; /* Same priority, wait for current stream to end */ - } - - /* This assigment must be thread-safe */ - __disable_irq(); - priority = prio; - running = true; - __enable_irq(); - - /* - * Convert buffer elements from int16_t to unsigned 16 bit values ranging - * from 0 to 4096, as required by DAC. 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. Code below exploits - * Cortex M4 SIMD instructions for fast execution. - */ - uint32_t *data = ((uint32_t *) buf); - for(size_t i = 0; i < length/2; i++) - { - uint32_t value = __SADD16(data[i], 0x80008000); - data[i] = (value >> 4) & 0x0FFF0FFF; - } - - /* Handle last element in case of odd buffer length */ - if((length % 2) != 0) - { - int16_t value = buf[length - 1] + 32768; - buf[length - 1] = ((uint16_t) value) >> 4; - } - - /* 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 - */ - 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 = ((uint32_t) &(DAC->DHR12R1)); - DMA1_Stream5->M0AR = ((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 */ - | 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 = ((uint32_t) &(DAC->DHR12R2)); - DMA1_Stream6->M0AR = ((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 */ - | 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. - * AP1 frequency is 42MHz but timer runs at 84MHz, tick rate is 1MHz, - * reload register is configured based on desired sample rate. - */ - TIM7->PSC = 83; - TIM7->ARR = (1000000/sampleRate) - 1; - TIM7->CNT = 0; - TIM7->EGR = TIM_EGR_UG; - TIM7->CR2 = TIM_CR2_MMS_1; - TIM7->CR1 = TIM_CR1_CEN; - - return 0; -} - -void outputStream_stop(streamId id) -{ - (void) id; - - if(!running) return; - - stopTransfer(); -} diff --git a/platform/drivers/audio/outputStream_Mod17.cpp b/platform/drivers/audio/outputStream_Mod17.cpp new file mode 100644 index 00000000..358cef40 --- /dev/null +++ b/platform/drivers/audio/outputStream_Mod17.cpp @@ -0,0 +1,310 @@ +/*************************************************************************** + * 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 + +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() +{ + // Shutdown timer + TIM7->CR1 &= ~TIM_CR1_CEN; + + // Disable DAC channels and clear underrun flags + DAC->CR &= ~(DAC_CR_EN1 | DAC_CR_EN2); + DAC->SR |= DAC_SR_DMAUDR1 | DAC_SR_DMAUDR2; + + // Stop DMA transfers + DMA1_Stream5->CR &= ~DMA_SxCR_EN; + DMA1_Stream6->CR &= ~DMA_SxCR_EN; + + running = 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 + if(DMA1->HISR & 0x00000F40) + { + DMA1->HIFCR = DMA_HIFCR_CTEIF5 + | DMA_HIFCR_CTCIF5 + | DMA_HIFCR_CHTIF5; + } + + // Clear interrupt flags for stream 6 + if(DMA1->HISR & 0x003D0000) + { + DMA1->HIFCR = DMA_HIFCR_CTEIF6 + | DMA_HIFCR_CTCIF6 + | DMA_HIFCR_CHTIF6; + } + + // 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. + * AP1 frequency is 42MHz but timer runs at 84MHz, tick rate is 1MHz, + * reload register is configured based on desired sample rate. + */ + TIM7->CNT = 0; + TIM7->PSC = 0; + TIM7->ARR = 84000000/sampleRate; + 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; +}