How to Use ADC with DMA in Embedded C: Simple Guide
To use
ADC with DMA in embedded C, configure the ADC to trigger conversions and set up DMA to automatically transfer ADC data to memory without CPU intervention. This involves initializing both peripherals, linking DMA to ADC data register, and starting the ADC with DMA enabled.Syntax
The basic steps to use ADC with DMA are:
- Initialize ADC peripheral.
- Initialize DMA peripheral and link it to ADC data register.
- Configure DMA transfer parameters (source, destination, size).
- Enable ADC DMA mode.
- Start ADC conversion with DMA.
This setup allows ADC to fill a memory buffer automatically via DMA.
c
void ADC_DMA_Init(void) { // 1. Initialize ADC ADC_InitTypeDef ADC_InitStruct = {0}; ADC_InitStruct.Resolution = ADC_RESOLUTION_12B; ADC_InitStruct.ScanConvMode = DISABLE; ADC_InitStruct.ContinuousConvMode = ENABLE; ADC_InitStruct.DataAlign = ADC_DATAALIGN_RIGHT; ADC_InitStruct.ExternalTrigConv = ADC_SOFTWARE_START; HAL_ADC_Init(&hadc1); // 2. Initialize DMA linked to ADC DMA_HandleTypeDef hdma_adc = {0}; hdma_adc.Instance = DMA1_Channel1; hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc.Init.MemInc = DMA_MINC_ENABLE; hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc.Init.Mode = DMA_CIRCULAR; hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_adc); __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc); // 3. Enable ADC DMA mode HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_LENGTH); }
Example
This example shows how to configure ADC with DMA to read multiple samples into a buffer continuously. It demonstrates initializing peripherals, starting ADC with DMA, and processing the data.
c
#include "stm32f1xx_hal.h" #define ADC_BUFFER_LENGTH 10 uint16_t adc_buffer[ADC_BUFFER_LENGTH]; ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; void SystemClock_Config(void); void MX_GPIO_Init(void); void MX_DMA_Init(void); void MX_ADC1_Init(void); int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_DMA_Init(); MX_ADC1_Init(); // Start ADC with DMA HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_LENGTH); while (1) { // adc_buffer is filled automatically by DMA // Process adc_buffer data here HAL_Delay(1000); } } void MX_ADC1_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; 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; HAL_ADC_Init(&hadc1); sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5; HAL_ADC_ConfigChannel(&hadc1, &sConfig); } void MX_DMA_Init(void) { __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_CIRCULAR; hdma_adc1.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hdma_adc1); __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { // Called when DMA buffer is full // You can process adc_buffer here } void SystemClock_Config(void) { // System clock config code here } void MX_GPIO_Init(void) { // GPIO init code here }
Output
No direct output; adc_buffer is filled continuously with ADC data via DMA.
Common Pitfalls
Common mistakes when using ADC with DMA include:
- Not linking DMA handle to ADC handle, causing DMA not to start.
- Incorrect DMA configuration like wrong data alignment or buffer size.
- Forgetting to enable DMA clock or ADC clock.
- Not starting ADC with DMA function (
HAL_ADC_Start_DMA). - Using non-circular DMA mode when continuous sampling is needed.
Always check peripheral clocks and interrupt priorities if used.
c
/* Wrong: Not linking DMA to ADC */ // HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_LENGTH); // but forgot __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); /* Right: Link DMA handle before starting ADC DMA */ __HAL_LINKDMA(&hadc1, DMA_Handle, hdma_adc1); HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_LENGTH);
Quick Reference
Tips for using ADC with DMA:
- Use circular DMA mode for continuous ADC sampling.
- Ensure DMA and ADC clocks are enabled before initialization.
- Link DMA handle to ADC handle with
__HAL_LINKDMA. - Start ADC with DMA using
HAL_ADC_Start_DMA(). - Process data in DMA complete callback or main loop.
Key Takeaways
Initialize and link ADC and DMA peripherals properly before starting conversion.
Use circular DMA mode for continuous ADC data transfer without CPU load.
Always start ADC with DMA using HAL_ADC_Start_DMA to enable automatic data transfer.
Check peripheral clocks and DMA configuration to avoid common setup errors.
Process ADC data from the DMA buffer in the main loop or DMA completion callback.