2023-05-08 08:57 AM
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.
Solved! Go to Solution.
2023-05-08 04:21 PM - edited 2023-11-20 06:01 AM
> 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!
JW
2023-05-08 01:41 PM
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
2023-05-08 03:34 PM
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.
2023-05-08 04:21 PM - edited 2023-11-20 06:01 AM
> 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!
JW
2023-05-08 04:57 PM
Thank you very much! I figured it would be a detail I overlooked. Works flawlessly now.