diff --git a/platform/drivers/audio/stm32_adc.cpp b/platform/drivers/audio/stm32_adc.cpp new file mode 100644 index 00000000..d5920b42 --- /dev/null +++ b/platform/drivers/audio/stm32_adc.cpp @@ -0,0 +1,271 @@ +/*************************************************************************** + * Copyright (C) 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 * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include "stm32_adc.h" + +struct AdcPeriph +{ + ADC_TypeDef *adc; + StreamHandler *stream; + Timer tim; +}; + +using Dma2_Stream0 = DmaStream< DMA2_BASE, 0, 0, 2 >; // DMA 2, Stream 2, channel 0, high priority (ADC1) +using Dma2_Stream1 = DmaStream< DMA2_BASE, 1, 2, 2 >; // DMA 2, Stream 2, channel 2, high priority (ADC3) +using Dma2_Stream2 = DmaStream< DMA2_BASE, 2, 1, 2 >; // DMA 2, Stream 2, channel 1, high priority (ADC2) + +StreamHandler Dma2_Stream0_hdl = Dma2_Stream0::init(10, DataSize::_16BIT, false); +StreamHandler Dma2_Stream1_hdl = Dma2_Stream1::init(10, DataSize::_16BIT, false); +StreamHandler Dma2_Stream2_hdl = Dma2_Stream2::init(10, DataSize::_16BIT, false); + +struct streamCtx *AdcContext[3]; + +static constexpr AdcPeriph periph[] = +{ + {(ADC_TypeDef *) ADC1_BASE, &Dma2_Stream0_hdl, Timer(TIM2_BASE)}, + {(ADC_TypeDef *) ADC2_BASE, &Dma2_Stream2_hdl, Timer(TIM2_BASE)}, + {(ADC_TypeDef *) ADC3_BASE, &Dma2_Stream1_hdl, Timer(TIM2_BASE)}, +}; + + +/** + * \internal + * Stop an ongoing transfer, deactivating timers and DMA stream. + */ +static void stopTransfer(const uint8_t instNum) +{ + periph[instNum].tim.stop(); + periph[instNum].adc->CR2 &= ~ADC_CR2_ADON; + AdcContext[instNum]->running = 0; +} + +/** + * \internal + * Actual implementation of DMA interrupt handler. + */ +void __attribute__((used)) ADC_DMA_Handler(uint32_t instNum) +{ + switch(instNum) + { + case 0: + Dma2_Stream0::IRQhandleInterrupt(periph[instNum].stream); + break; + + case 1: + Dma2_Stream2::IRQhandleInterrupt(periph[instNum].stream); + break; + + case 2: + Dma2_Stream1::IRQhandleInterrupt(periph[instNum].stream); + break; + } +} + +// DMA 2, Stream 0: data transfer for ADC1 +void __attribute__((used)) DMA2_Stream0_IRQHandler() +{ + saveContext(); + asm volatile("mov r0, #0"); + asm volatile("bl _Z15ADC_DMA_Handlerm"); + restoreContext(); +} + +// DMA 2, Stream 2: data transfer for ADC2 +void __attribute__((used)) DMA2_Stream2_IRQHandler() +{ + saveContext(); + asm volatile("mov r0, #1"); + asm volatile("bl _Z15ADC_DMA_Handlerm"); + restoreContext(); +} + +// DMA 2, Stream 1: data transfer for ADC3 +void __attribute__((used)) DMA2_Stream1_IRQHandler() +{ + saveContext(); + asm volatile("mov r0, #2"); + asm volatile("bl _Z15ADC_DMA_Handlerm"); + restoreContext(); +} + + + +void stm32adc_init(const uint8_t instance) +{ + // Enable peripherals + switch(instance) + { + case 0: + RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; + break; + + case 1: + RCC->APB2ENR |= RCC_APB2ENR_ADC2EN; + break; + + case 2: + RCC->APB2ENR |= RCC_APB2ENR_ADC3EN; + break; + } + + /* + * Use TIM2 as trigger source for all the ADCs. + * TODO: change this with a dedicated timer for each ADC. + */ + RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; + uint32_t adcTrig = ADC_CR2_EXTSEL_1 // 0b0110 TIM2_TRGO trig. source + | ADC_CR2_EXTSEL_2; + + __DSB(); + + /* + * ADC 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_TypeDef *adc = periph[instance].adc; + + ADC->CCR |= ADC_CCR_ADCPRE_0; + adc->SMPR2 = ADC_SMPR2_SMP2_2 + | ADC_SMPR2_SMP1_2; + adc->SQR1 = 0; // Convert one channel + adc->CR1 |= ADC_CR1_DISCEN; + adc->CR2 |= ADC_CR2_EXTEN_0 // Trigger on rising edge + | adcTrig // Trigger source + | ADC_CR2_DDS // Continuous DMA requests + | ADC_CR2_DMA; // Enable DMA data transfer + + // Register end-of-transfer callbacks + periph[instance].stream->setEndTransferCallback(std::bind(stopTransfer, instance)); +} + +void stm32adc_terminate() +{ + // Terminate streams before shutting of the peripherals + for(int i = 0; i < 2; i++) + { + if(AdcContext[i] != NULL) + { + if(AdcContext[i]->running != 0) + periph[i].stream->halt(); + } + } + + // TODO: turn off peripherals + RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN; + __DSB(); +} + +static int stm32adc_start(const uint8_t instance, const void *config, struct streamCtx *ctx) +{ + if(ctx == NULL) + return -EINVAL; + + if((ctx->running != 0) || (periph[instance].stream->running())) + return -EBUSY; + + __disable_irq(); + ctx->running = 1; + __enable_irq(); + + ctx->priv = (void *) &periph[instance]; + AdcContext[instance] = ctx; + + // Set conversion channel and enable the ADC + periph[instance].adc->SQR3 = (uint32_t) config; + periph[instance].adc->CR2 |= ADC_CR2_ADON; + + // Start DMA stream + bool circ = false; + if(ctx->bufMode == BUF_CIRC_DOUBLE) + circ = true; + + periph[instance].stream->start(&(periph[instance].adc->DR), ctx->buffer, + ctx->bufSize, circ); + + // Configure ADC trigger + periph[instance].tim.setUpdateFrequency(ctx->sampleRate); + periph[instance].tim.start(); + + return 0; +} + +static int stm32adc_data(struct streamCtx *ctx, stream_sample_t **buf) +{ + AdcPeriph *p = reinterpret_cast< AdcPeriph * >(ctx->priv); + + if(ctx->bufMode == BUF_CIRC_DOUBLE) + { + *buf = reinterpret_cast< stream_sample_t *>(p->stream->idleBuf()); + return ctx->bufSize/2; + } + + // Linear mode: return the full buffer + *buf = ctx->buffer; + return ctx->bufSize; +} + +static int stm32adc_sync(struct streamCtx *ctx, uint8_t dirty) +{ + (void) dirty; + + AdcPeriph *p = reinterpret_cast< AdcPeriph * >(ctx->priv); + bool ok = p->stream->sync(); + if(ok) + return 0; + + return -1; +} + +static void stm32adc_stop(struct streamCtx *ctx) +{ + if(ctx->running == 0) + return; + + reinterpret_cast< AdcPeriph * >(ctx->priv)->stream->stop(); +} + +static void stm32adc_halt(struct streamCtx *ctx) +{ + if(ctx->running == 0) + return; + + reinterpret_cast< AdcPeriph * >(ctx->priv)->stream->halt(); +} + +#pragma GCC diagnostic ignored "-Wpedantic" +const struct audioDriver stm32_adc_audio_driver = +{ + .start = stm32adc_start, + .data = stm32adc_data, + .sync = stm32adc_sync, + .stop = stm32adc_stop, + .terminate = stm32adc_halt +}; +#pragma GCC diagnostic pop diff --git a/platform/drivers/audio/stm32_adc.h b/platform/drivers/audio/stm32_adc.h new file mode 100644 index 00000000..e2dfed40 --- /dev/null +++ b/platform/drivers/audio/stm32_adc.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (C) 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 * + ***************************************************************************/ + +#ifndef STM32_ADC_H +#define STM32_ADC_H + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Driver for STM32F4xx ADC peripheral used as audio input stream device. + * Output data format is signed 16-bit, hardware outputs positive-only 12-bit + * samples. + * + * This driver has three instances: + * + * - instance 0: ADC1 + * - instance 1: ADC2 + * - instance 2: ADC3 + * + * The configuration parameter for each instance is the ADC input number. + */ + + +enum Stm32AdcInstance +{ + STM32_ADC_ADC1 = 0, + STM32_ADC_ADC2, + STM32_ADC_ADC3, +}; + + +extern const struct audioDriver stm32_adc_audio_driver; + + +/** + * Initialize the driver and the peripherals. + */ +void stm32adc_init(const uint8_t instance); + +/** + * Shutdown the driver and the peripherals. + */ +void stm32adc_terminate(); + +#ifdef __cplusplus +} +#endif + +#endif /* STM32_DAC_H */