Page 1 of 1

STM32G030 how to use DMA to read ADC faster?

Posted: Thu Feb 13, 2025 11:27 am
by dusan82
I need to read ADC faster than analogRead can, so I need to use DMA. Here is my code. It doesn't work it prints all zeros:

Code: Select all

#include "stm32g0xx_hal.h"

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

uint32_t buffer[50];

void init_adc_to_use_dma() {
  // Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK) {
    Serial.println("some error 1");
  }
  // Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time
  ADC_ChannelConfTypeDef sConfig = {0};
  sConfig.Channel = ADC_CHANNEL_16;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5; // stm32g0xx_hal_adc.h has all possible values 
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    Serial.println("some error 2");
  }  
  // dma?
  __HAL_RCC_DMA1_CLK_ENABLE();
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  delay(500);
  HAL_ADC_Start_DMA(&hadc1,(uint32_t*) &buffer, 50);
  Serial.println("done");
}

void setup() {
  Serial.begin(115200); 
  delay(500);
  init_adc_to_use_dma();
}

void loop() {
  delay(1000);
  for (int i = 0; i < 50; i++) {
    Serial.print(i);
    Serial.print(' ');
    Serial.println(buffer[i]);
  }
}
Any idea how to fix it? Thanks.

Re: STM32G030 how to use DMA to read ADC faster?

Posted: Thu Feb 13, 2025 11:55 am
by ag123
I think you may be missing out some codes that configure the DMA.
That need to be setup as well and linked to the ADC HAL configs.

Re: STM32G030 how to use DMA to read ADC faster?

Posted: Thu Feb 13, 2025 12:28 pm
by dusan82
Here is updated code but still all zeros:

Code: Select all

#include "stm32g0xx_hal.h"

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

uint32_t buffer[50];

void init_adc_to_use_dma() {
  // Initialize the ADC
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK) {
    Serial.println("some error 1");
  }

  // Configure ADC channel
  ADC_ChannelConfTypeDef sConfig = {0};
  sConfig.Channel = ADC_CHANNEL_16;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    Serial.println("some error 2");
  }

  // Initialize the DMA
  __HAL_RCC_DMA1_CLK_ENABLE();
  hdma_adc1.Instance = DMA1_Channel1;
  hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
  hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
  hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
  hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
  hdma_adc1.Init.Mode = DMA_CIRCULAR;
  hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;

  if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) {
    Serial.println("some error 3");
  }

  __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

  // Enable the DMA interrupt
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  
  // Start ADC with DMA
  delay(500);
  if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&buffer, 50) != HAL_OK) {
    Serial.println("some error 4");
  }
  Serial.println("done");
}

void setup() {
  pinMode(PA0, INPUT);
  Serial.begin(115200); 
  delay(500);
  init_adc_to_use_dma();
}

void loop() {
  delay(1000);
  for (int i = 0; i < 50; i++) {
    Serial.print(i);
    Serial.print(' ');
    Serial.println(buffer[i]);
  }
}

void DMA1_Channel1_IRQHandler(void) {
  Serial.println("DMA1_Channel1_IRQHandler");
  HAL_DMA_IRQHandler(&hdma_adc1);
}

Re: STM32G030 how to use DMA to read ADC faster?

Posted: Thu Feb 13, 2025 6:02 pm
by ag123
not too sure,

ADC channel 16 seemed to be on PB12
https://www.st.com/resource/en/datashee ... g030c6.pdf
it may not be there on some (smaller) chips.
And you may need to configure the pins for analog input, e.g. try

Code: Select all

pinMode(PB12, INPUT_ANALOG);
you can also try to read the internal temperature sensor, I think it may be on ADC ch 12, check the ref manual. There is also the vref that you can read on a different channel.
https://www.st.com/resource/en/referenc ... ronics.pdf
and you also need to enable the temperature sensor to read it.

Re: STM32G030 how to use DMA to read ADC faster?

Posted: Thu Feb 13, 2025 6:43 pm
by fpiSTM
Add extern "C" before the handler and so not call Serial inside.

Code: Select all

extern "C" void DMA1_Channel1_IRQHandler(void) {
  HAL_DMA_IRQHandler(&hdma_adc1);
}

Re: STM32G030 how to use DMA to read ADC faster?

Posted: Thu Feb 13, 2025 6:51 pm
by dusan82
- I added pinMode(*, INPUT_ANALOG) to all 16 adc pins just to be sure and set voltage there to approx 2V
- I added extern C
- still nothing, all zeroes
- if I comment out init_adc_to_use_dma(); and use analogRead(PA0) or any other pin it shows some value, but if I call init_adc_to_use_dma() even analogRead starts returning 0

Code: Select all

#include "stm32g0xx_hal.h"

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

uint32_t buffer[50];

void init_adc_to_use_dma() {
  // Initialize the ADC
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = ENABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK) {
    Serial.println("some error 1");
  }

  // Configure ADC channel
  ADC_ChannelConfTypeDef sConfig = {0};
  sConfig.Channel = ADC_CHANNEL_16;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_12CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
    Serial.println("some error 2");
  }

  // Initialize the DMA
  __HAL_RCC_DMA1_CLK_ENABLE();
  hdma_adc1.Instance = DMA1_Channel1;
  hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
  hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
  hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
  hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
  hdma_adc1.Init.Mode = DMA_CIRCULAR;
  hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;

  if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) {
    Serial.println("some error 3");
  }

  __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);

  // Enable the DMA interrupt
  HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 0, 0);
  HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
  
  // Start ADC with DMA
  delay(500);
  if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&buffer, 50) != HAL_OK) {
    Serial.println("some error 4");
  }
  Serial.println("done");
}

void setup() {
  pinMode(PA0, INPUT);
  Serial.begin(115200); 
  delay(500);
  pinMode(PA0, INPUT_ANALOG);
  pinMode(PA1, INPUT_ANALOG);
  pinMode(PA2, INPUT_ANALOG);
  pinMode(PA3, INPUT_ANALOG);
  pinMode(PA4, INPUT_ANALOG);
  pinMode(PA5, INPUT_ANALOG);
  pinMode(PA6, INPUT_ANALOG);
  pinMode(PA7, INPUT_ANALOG);
  pinMode(PB0, INPUT_ANALOG);
  pinMode(PB1, INPUT_ANALOG);
  pinMode(PB2, INPUT_ANALOG);
  pinMode(PB10, INPUT_ANALOG);
  pinMode(PB11, INPUT_ANALOG);
  pinMode(PB12, INPUT_ANALOG);
  init_adc_to_use_dma();
}

void loop() {
  delay(1000);
  for (int i = 0; i < 50; i++) {
    Serial.print(i);
    Serial.print(' ');
    Serial.println(buffer[i]);
  }
}

extern "C" void DMA1_Channel1_IRQHandler(void) {
  HAL_DMA_IRQHandler(&hdma_adc1);
}

Re: STM32G030 how to use DMA to read ADC faster?

Posted: Fri Feb 14, 2025 2:26 pm
by dusan82
I continued working on it, most examples on internet were for F103 so I switched to F103 and eventually made it work, here is code that reads 2000 samples at 400kHz on F103 using DMA. The HAL_ADC_ConvCpltCallback is not triggered so I just put delay(100) there. After pressing "enter" in serial console it captures 2000 samples and print them to output.

Code: Select all

// Read 12bit ADC samples from pin PA0 of STM32F103C8T6 (blue pill) at 400kHz using DMA

#include "stm32f1xx.h"
#include "stm32f1xx_hal_def.h"

// number of samples
#define BUF_SIZE 2000   
uint16_t buffer[BUF_SIZE];

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

void MX_ADC1_Init(void) {
  // Configure ADC1
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = ENABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK) {
    Serial.println("HAL_ADC_Init() failed!");
    return;
  }
    
  // Configure Regular Channel
  ADC_ChannelConfTypeDef sConfig = {0};
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_13CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){
    Serial.println("HAL_ADC_ConfigChannel() failed!");
    return;
  }

  // Configure DMA for ADC1
  __HAL_RCC_DMA1_CLK_ENABLE();
  hdma_adc1.Instance = DMA1_Channel1;
  hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
  hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
  hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
  hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
  hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
  hdma_adc1.Init.Mode = DMA_NORMAL; //DMA_CIRCULAR;
  hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
  if (HAL_DMA_Init(&hdma_adc1) != HAL_OK) {
    Serial.println("HAL_DMA_Init() failed!");
    return;
  }

  // Link DMA to ADC
  __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1);
}

extern "C" void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) {
  // This should be called at the end of the conversion but it's not
  // for now I know how long it takes to measure samples so I just wait
  Serial.println("adc conversion completed");
  if (hadc->Instance == ADC1) {
    Serial.println("adc1 conversion completed");
  }
}

void analogReadFastA0(uint16_t* buffer, int size) {
  // read samples into buffer
  // Stop ADC and DMA after reading the first sample
  HAL_ADC_Stop_DMA(&hadc1);
  // Clear the buffer
  for (int i = 0; i < size; i++) {
    buffer[i] = 0;
  }
  // Restart ADC and DMA for the next sample and entire buffer
  if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)buffer, BUF_SIZE) != HAL_OK) {
    Serial.println("HAL_ADC_Start_DMA() failed!");
    return;
  }
}   

void setup() {
  Serial.begin(115200);
  pinMode(PA0, INPUT_ANALOG);
  MX_ADC1_Init();
  Serial.println("Press enter to read one block");
}
   
void loop() {
  if (Serial.available()) {
    int c = Serial.read();

    if (c == 13) {
      analogReadFastA0(&buffer[0], BUF_SIZE);
      delay(100);
      for (int i = 0; i < BUF_SIZE; i += 5) {  // I'm writing only every 5th sample to fit serial plotter
        Serial.println(buffer[i]);
      }
    }
  }
}

Re: STM32G030 how to use DMA to read ADC faster?

Posted: Fri Feb 14, 2025 4:44 pm
by fpiSTM
Have you seen this:
viewtopic.php?t=110