cancel
Showing results for 
Search instead for 
Did you mean: 

How can I configure DMA to transfer an ADC sample triggered by a timer?

bsakas
Associate II

Hello,

I am currently trying to set up a minimal example of using DMA for ADC conversions on a NUCLEO-F303K8 development board. I'm using the CMSIS device headers rather than the HAL, so I apologize if my issue is due to misunderstanding something in the reference manual.

I have been able to set up the timer and ADC to regularly trigger the ADC interrupt on end of conversion and read the data, but I have been unsuccessful in implementing the DMA equivalent -- triggering a DMA transfer on end of conversion directly to RAM. I have been receiving DMA full transfer interrupts as expected (on the timing interval + conversion time), but the ADC reading transferred by DMA is garbage.

Here's my full program:

#include <stdbool.h>
#include <stdint.h>
 
#include "stm32f3xx.h"
#include "core_cm4.h"
 
#define ADC ADC1
 
#define ADC_DMA_CH DMA1_Channel1
#define ADC_DMA_IRQ DMA1_Channel1_IRQn
 
#define ADC_GPIO_PORT GPIOA
#define ADC_GPIO_PIN 0
#define GPIO_MODE_ANALOG 0b11
 
#define ADC_TIMER TIM2
#define ADC_TIMER_PRESCALER 7999 // time base of 1ms
#define ADC_TIMER_RELOAD 2000
#define ADC_TIMER_MMS 0b010 // update event is TRGO
 
#define ADC_CLOCK_MODE 0b01 // HCLK/1 (synchronous)
#define ADC_TRIG_EDGE 0b01 // rising edge
#define ADC_TRIG_SELECT 11 // EXT11 = TIM2_TRGO
 
// total conversion time 614 cycles ~ 77us
#define ADC_SAMP_TIME 0b111
 
#define DMA_MSIZE 0b10 // 32 bits
#define DMA_PSIZE 0b01 // 16 bits
 
#define USE_DMA 1 // use ADC interrupt otherwise
 
static volatile uint32_t adc_reading = 0;
 
void DMA1_Channel1_IRQHandler(void)
{
  // clear global, half transfer + full transfer of channel 1
  DMA1->IFCR |= 0b111 << DMA_IFCR_CGIF1_Pos;
}
 
void ADC1_2_IRQHandler(void)
{
  adc_reading = ADC->DR;
}
 
void adc_dma_init(void)
{
  RCC->AHBENR |= RCC_AHBENR_DMA1EN_Msk;
  ADC_DMA_CH->CCR |= (DMA_MSIZE << DMA_CCR_MSIZE_Pos) 
                   | (DMA_PSIZE << DMA_CCR_PSIZE_Pos) 
                   | DMA_CCR_CIRC_Msk 
                   | DMA_CCR_TCIE_Msk;
  ADC_DMA_CH->CPAR = (uint32_t)&(ADC->DR);
  ADC_DMA_CH->CMAR = (uint32_t)&adc_reading;
  ADC_DMA_CH->CNDTR = 1;
  ADC_DMA_CH->CCR |= DMA_CCR_EN_Msk;
  NVIC_EnableIRQ(ADC_DMA_IRQ);
}
 
void adc_gpio_init(void)
{
  RCC->AHBENR |= RCC_AHBENR_GPIOAEN_Msk;
  ADC_GPIO_PORT->MODER |= GPIO_MODE_ANALOG << (2 * ADC_GPIO_PIN);
}
 
void adc_timer_init(void)
{
  RCC->APB1ENR |= RCC_APB1ENR_TIM2EN_Msk;
  ADC_TIMER->CR2 |= ADC_TIMER_MMS << TIM_CR2_MMS_Pos;
  ADC_TIMER->CNT = 0;
  ADC_TIMER->PSC = ADC_TIMER_PRESCALER;
  ADC_TIMER->ARR = ADC_TIMER_RELOAD;
  ADC_TIMER->EGR |= TIM_EGR_UG_Msk;
}
 
void adc_vreg_enable(void)
{
  RCC->APB1ENR |= RCC_APB1ENR_TIM3EN_Msk;
  TIM3->CR1 |= TIM_CR1_OPM_Msk | TIM_CR1_URS_Msk;
  TIM3->CNT = 0;
  TIM3->PSC = 7;
  TIM3->ARR = 10;
  TIM3->EGR |= TIM_EGR_UG_Msk;
 
  ADC->CR &= ~(ADC_CR_ADVREGEN_Msk);
  ADC->CR |= 0b01 << ADC_CR_ADVREGEN_Pos;
 
  // 10us delay
  TIM3->CR1 |= TIM_CR1_CEN_Msk;
  while (!(TIM3->SR & TIM_SR_UIF_Msk));
}
 
void adc_init(void)
{
  RCC->AHBENR |= RCC_AHBENR_ADC12EN_Msk;
  ADC12_COMMON->CCR |= ADC_CLOCK_MODE << ADC12_CCR_CKMODE_Pos;
 
  adc_vreg_enable();
 
  ADC->CFGR |= (ADC_TRIG_EDGE << ADC_CFGR_EXTEN_Pos)
             | (ADC_TRIG_SELECT << ADC_CFGR_EXTSEL_Pos);
 
  // single conversion on channel ADC1_IN1 (A0)
  ADC->SMPR1 |= (ADC_SAMP_TIME << ADC_SMPR1_SMP1_Pos);
  ADC->SQR1 |= 1 | (1 << ADC_SQR1_SQ1_Pos);
  
  if (USE_DMA)
  {
    ADC->CFGR |= ADC_CFGR_DMAEN_Msk | ADC_CFGR_DMACFG_Msk;
  }
  else
  {
    ADC->IER |= ADC_IER_EOCIE_Msk;
    NVIC_EnableIRQ(ADC1_2_IRQn);
  }
 
  ADC->CR |= ADC_CR_ADEN_Msk;
  while (!(ADC->ISR & ADC_ISR_ADRDY_Msk));
}
 
int main(void) 
{
  if (USE_DMA)
  {
    adc_dma_init();
  }
  adc_gpio_init();
  adc_timer_init();
  adc_init();
 
  // required before waiting for first hardware trigger
  ADC->CR |= ADC_CR_ADSTART_Msk;
 
  ADC_TIMER->CR1 |= TIM_CR1_CEN_Msk;
 
  while (1);
}

Using DMA results in adc_reading jumping around 0xFFF - as if my analog input voltage was at the analog supply voltage or saturating. Using the ADC interrupt results in adc_reading holding the proper converted value, for example 0x800 at half analog supply voltage.

I have tried changing the order of configuration to no avail. I haven't tried different combinations of timers or ADC gpios yet, since I am hoping it is a software mistake. The analog signal comes from a potentiometer.

Thanks in advance for any pointers on how to proceed, and let me know if I can provide any other information that may help.

1 ACCEPTED SOLUTION

Accepted Solutions

> Now, adc_readings[0] contains the correct reading on every DMA transfer,

> and adc_readings[1] contains the same garbage value as before.

Now it makes sense!


_legacyfs_online_stmicro_images_0693W00000bjdD2QAI.png 

JW

View solution in original post

4 REPLIES 4

This is a genuine mystery.

I see absolutely no problem with the code (but did not try to run).

Which STM32 exactly and what hardware?

Is VREF+/VREF-/VDDA/VSSA (if applicable) properly connected and rock stable?

If you try to read out ADC_DR in the DMA ISR, what's the result?

Other things to try, although most probably of no help: symmetrical PSIZE and MSIZE, non-circular DMA, larger buffer.

JW

I am using the STM32F303K8T6 on a Nucleo board (reference board MB1180.) According to the schematic, VDDA is connected to VDD at 3.3V from the on-board regulator and VSSA is connected to ground. The lines look stable as far as I can tell.

Reading out the data register in the DMA ISR gives the same value as stored in adc_reading.

Symmetrical PSIZE and MSIZE did not seem to have an effect, but I did get some progress by changing adc_reading to a buffer of size 2:

static volatile uint32_t adc_readings[2] = {};

With DMA updated to auto increment MEM and read 2 words:

  ...
  ADC_DMA_CH->CCR |= (DMA_MSIZE << DMA_CCR_MSIZE_Pos) 
                   | (DMA_PSIZE << DMA_CCR_PSIZE_Pos) 
                   | DMA_CCR_CIRC_Msk
                   | DMA_CCR_TCIE_Msk
                   | DMA_CCR_MINC_Msk;
  ADC_DMA_CH->CPAR = (uint32_t)&(ADC->DR);
  ADC_DMA_CH->CMAR = (uint32_t)adc_readings;
  ADC_DMA_CH->CNDTR = 2;
  ...

Now, adc_readings[0] contains the correct reading on every DMA transfer, and adc_readings[1] contains the same garbage value as before. I'm afraid I don't understand why this is occurring, and while it works, I wouldn't consider it a solution. Any ideas on what else I can try? I will try to run the same setup on a different board to see if it is some obscure hardware issue.

> Now, adc_readings[0] contains the correct reading on every DMA transfer,

> and adc_readings[1] contains the same garbage value as before.

Now it makes sense!


_legacyfs_online_stmicro_images_0693W00000bjdD2QAI.png 

JW

Thank you very much! I figured it would be a detail I overlooked. Works flawlessly now.