OpenRTX/platform/drivers/audio/stm32_adc.cpp

354 lines
10 KiB
C++

/***************************************************************************
* Copyright (C) 2023 - 2025 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 <core/cache_cortexMx.h>
#include <interfaces/delays.h>
#include <peripherals/gpio.h>
#include <DmaStream.hpp>
#include <hwconfig.h>
#include <miosix.h>
#include <errno.h>
#include "stm32_adc.h"
#if defined(STM32H743xx)
#include <Lptim.hpp>
typedef Lptim timebase_type;
#define TimebaseCh1 Lptim(LPTIM3_BASE, 168000000)
#define TimebaseCh2 Lptim(LPTIM3_BASE, 168000000)
#define TimebaseCh3 Lptim(LPTIM3_BASE, 168000000)
#else
#include <Timer.hpp>
typedef Timer timebase_type;
#define TimebaseCh1 Timer(TIM2_BASE)
#define TimebaseCh2 Timer(TIM2_BASE)
#define TimebaseCh3 Timer(TIM2_BASE)
#endif
struct AdcPeriph
{
ADC_TypeDef *adc;
StreamHandler *stream;
timebase_type 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);
static struct streamCtx *AdcContext[3];
static constexpr AdcPeriph periph[] =
{
{(ADC_TypeDef *) ADC1_BASE, &Dma2_Stream0_hdl, TimebaseCh1},
{(ADC_TypeDef *) ADC2_BASE, &Dma2_Stream2_hdl, TimebaseCh2},
{(ADC_TypeDef *) ADC3_BASE, &Dma2_Stream1_hdl, TimebaseCh3},
};
/**
* \internal
* Stop an ongoing transfer, deactivating timers and DMA stream.
*/
static void stopTransfer(const uint8_t instNum)
{
ADC_TypeDef *adc = periph[instNum].adc;
#ifdef STM32H743xx
adc->CR |= ADC_CR_ADSTP;
while((adc->CR & ADC_CR_ADSTP) != 0) ;
#else
adc->CR2 &= ~ADC_CR2_ADON;
#endif
periph[instNum].tim.stop();
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 STM32_ADC_ADC1:
#ifdef STM32H743xx
RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN;
ADC12_COMMON->CCR = ADC_CCR_CKMODE_1
| ADC_CCR_CKMODE_0;
DMAMUX1_Channel8->CCR = 9;
#else
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
#endif
break;
case STM32_ADC_ADC2:
#ifdef STM32H743xx
RCC->AHB1ENR |= RCC_AHB1ENR_ADC12EN;
ADC12_COMMON->CCR = ADC_CCR_CKMODE_1
| ADC_CCR_CKMODE_0;
DMAMUX1_Channel10->CCR = 10;
#else
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
#endif
break;
case STM32_ADC_ADC3:
#ifdef STM32H743xx
RCC->AHB4ENR |= RCC_AHB4ENR_ADC3EN;
ADC3_COMMON->CCR = ADC_CCR_CKMODE_1
| ADC_CCR_CKMODE_0;
DMAMUX1_Channel9->CCR = 115;
#else
RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
#endif
break;
}
/*
* Use TIM2 as trigger source for all the ADCs.
* TODO: change this with a dedicated timer for each ADC.
*/
#ifdef STM32H743xx
RCC->APB4ENR |= RCC_APB4ENR_LPTIM3EN;
RCC->D3CCIPR |= RCC_D3CCIPR_LPTIM345SEL_0;
uint32_t adcTrig = 20 << ADC_CFGR_EXTSEL_Pos; // lptim3_out as trig. source
#else
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
ADC->CCR |= ADC_CCR_ADCPRE_0;
uint32_t adcTrig = ADC_CR2_EXTSEL_2 // 0b0110 TIM2_TRGO trig. source
| ADC_CR2_EXTSEL_1;
#endif
__DSB();
ADC_TypeDef *adc = periph[instance].adc;
#ifdef STM32H743xx
adc->CR = ADC_CR_ADVREGEN
| ADC_CR_BOOST_1
| ADC_CR_BOOST_0;
while((adc->ISR & ADC_ISR_LDORDY) == 0) ;
adc->ISR = ADC_ISR_ADRDY; // Clear the ADRDY flag
adc->CR |= ADC_CR_ADEN;
while((adc->ISR & ADC_ISR_ADRDY) != 0) ;
adc->SMPR2 = 0x36DB6DB6;
adc->SMPR1 = 0x36DB6DB6;
adc->SQR1 = 0; // Convert one channel
adc->CFGR |= ADC_CFGR_DISCEN
| ADC_CFGR_EXTEN_0 // Trigger on rising edge
| adcTrig // Trigger source
| ADC_CFGR_RES_1 // 12-bit resolution
| ADC_CFGR_DMNGT_1 // Continuous DMA requests
| ADC_CFGR_DMNGT_0; // Enable DMA data transfer
#else
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
#endif
// 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
#ifdef STM32H743xx
RCC->APB1LENR &= ~RCC_APB1LENR_TIM2EN;
#else
RCC->APB1ENR &= ~RCC_APB1ENR_TIM2EN;
#endif
__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
ADC_TypeDef *adc = periph[instance].adc;
#ifdef STM32H743xx
uint32_t channel = (uint32_t) config;
adc->SQR1 = channel << ADC_SQR1_SQ1_Pos;
adc->PCSEL = 1 << channel;
adc->CR |= ADC_CR_ADSTART;
#else
adc->SQR3 = (uint32_t) config;
adc->CR2 |= ADC_CR2_ADON;
#endif
// 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);
// Default case: linear mode
*buf = ctx->buffer;
int size = ctx->bufSize;
if(ctx->bufMode == BUF_CIRC_DOUBLE)
{
*buf = reinterpret_cast< stream_sample_t *>(p->stream->idleBuf());
size = ctx->bufSize/2;
}
miosix::markBufferAfterDmaRead(*buf, size*sizeof(int16_t));
return size;
}
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