HomeSTM32 TutorialsSTM32 TutorialsADC SeriesSingle Channel Interrupt & DMA

STM32 ADC Single Channel with Interrupt and DMA

In the previous tutorial, we read a single ADC channel using polling mode. It was a simple approach where the CPU waits for each conversion to finish. In this tutorial, we take a step further and cover two more efficient methods: Interrupt mode and DMA mode. Both allow the ADC to run conversions in the background while the CPU is free to handle other tasks, making them far more suitable for real-world applications.

We will configure both modes in CubeMX, write the HAL code, and verify the output using the STM32CubeIDE debugger. We will also cover the Cortex-M7 specific MPU setup needed to handle cache coherency issues when using DMA on the STM32H750. The CubeMX configuration from Part 1 carries over here, so if you have not read that yet, start there first.

This is the 2nd tutorial in the STM32 ADC series. You can go through the other parts of this series, here are the links:

STM32 ADC Single Channel with Interrupt and DMA

STM32 ADC Potentiometer Wiring and Pin Connections

The image below shows the connection between STM32H750 and the Potentiometer.

Image shows the wiring between STM32H7 and a potentiometer. The potentiometer will be read using the ADC peripheral of the STM32 in interrupt and DMA mode.

The pin connection is described in the table below.

Potentiometer PinConnected To
VCC3.3V on STM32
GNDGND on STM32
OUTPA6 (ADC1_INP3) Pin

STM32 ADC Interrupt vs DMA Mode: Key Differences

ADC Interrupt Mode

When a conversion finishes, the ADC triggers an interrupt and the CPU steps in only at that moment to read the result. The rest of the time, the CPU is free to do other work. This makes interrupt mode significantly more efficient than polling, where the CPU sits idle waiting for the conversion to complete.

ADC DMA Mode

DMA takes CPU involvement a step further. Once started, the ADC transfers converted data directly into memory with no CPU interaction at all. This makes DMA the most efficient option for continuous or high-speed sampling, where triggering an interrupt for every single conversion would create unnecessary overhead.

Which One Should You Use?

FeatureInterrupt ModeDMA Mode
CPU InvolvementCPU reads data via ISR on each conversionCPU is completely free; DMA handles transfers
EfficiencyModerate — suitable for infrequent samplingHigh — ideal for continuous or high-speed data
ComplexitySimple to set upRequires additional DMA and buffer configuration
LatencySmall delay due to interrupt handlingVery low — transfers are handled in hardware
Best Use CaseOccasional or event-based ADC readingsHigh-frequency or large-volume data acquisition

A simple rule of thumb: if your application only needs ADC readings occasionally, interrupt mode is the easier and cleaner choice. If you need the ADC running continuously or at high speed, DMA is the better fit as it keeps the CPU free without the overhead of repeated interrupts. on the CPU. On the other hand, DMA mode is the better choice for continuous or high-speed sampling, since it transfers data directly to memory with almost no CPU overhead.

How to Use STM32 ADC in Interrupt Mode (HAL)

In interrupt mode, the ADC notifies the CPU only when a conversion is complete. This removes the need for constant polling and lets the CPU focus on other tasks until the interrupt is triggered. It is a good choice when sampling is occasional or when you want to save processing time compared to polling.

CubeMX Setup for ADC Interrupt Mode

The image below shows the cubeMX configuration for the Single channel Interrupt Mode.

STM32 ADC Interrupt mode Configuration
STM32 ADC Interrupt mode Enable in CubeMX

In the first image above, you can see that we need to enable Continuous Conversion Mode. This ensures that each new conversion starts right after the previous one finishes, so the process keeps running without interruption. In our case of a single channel, this setting makes sure the same channel is converted continuously, and the interrupt is triggered at a steady rate.

Next, go to the NVIC tab and enable the ADC Global Interrupt.


STM32 ADC Interrupt Mode HAL Code

The code will remain the same for all the STM32 MCUs. If there are any changes, I will specify in this section itself.

Definitions

Let’s define some variables, which we will use in the main function later.

uint16_t ADC_VAL = 0;
int value = 0;

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min + 1) / (in_max - in_min + 1) + out_min;
}

We define ADC_VAL as a 16-bit variable, and we use it to store the converted ADC value. Even if we select 10-bit, 12-bit, or 14-bit resolution, we still declare ADC_VAL as a 16-bit variable.

We will use the map function to map the converted ADC value to our desired range. The mapped value will be stored in the value variable, which I have defined as an integer here.


The main function

Below is the main function.

int main ()
{
  ....
  ....
  HAL_ADC_Start_IT(&hadc1);
  while (1)
  {
  }
}

Inside the main function, we will start the ADC in the interrupt mode. Once the ADC finishes the sampling and conversion of the data from the channel, an interrupt will trigger and the Conversion complete callback will be called. We will write the rest of the code inside this callback function.


ADC Conversion Complete Callback

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
	  ADC_VAL = HAL_ADC_GetValue(&hadc1);
	  value = map(ADC_VAL, 1700, 65535, 0, 100);
}

Inside the callback we will read the converted value using the function HAL_ADC_GetValue. This function returns the ADC value based on the resolution set during the configuration.

We now have the converted data stored in the ADC_VAL variable. We will use the map function to map the converted value to 0 to 100 range. Since I have set the ADC resolution to 16bits, the maximum value of the ADC_VAL variable will be 216-1 = 65535. Similarly, the maximum value for the 12bits resolution will be 212-1 = 4095 and the same for the 10bits resolution will be 210-1 = 1023.

I have set the minimum input value to the map function as 1700. This is because it is the value we were getting when the slider was at the extreme left. Similarly the maximum value to the input of the map function will be 65535, as this is the highest value for the 16 bit resolution.


Result of ADC in Interrupt Mode

The gif below shows the ADC values obtained by moving the potentiometer, in the STM32CubeIDE debugger.

Debugging STM32 ADC single channel interrupt mode in CubeIDE with potentiometer input connected on development board

As you can see in the image above, the ADC value is more responsive now. Also the value variable varies from 0 to 100 as we move the slider from left to right.

This means that the ADC is working pretty fine in the interrupt mode. One important thing you need to remember is not to set the ADC clock too high. If the conversion time is very low, the interrupts will trigger at an extremely high rate and this will make the while loop unusable.

Basically you have to tradeoff between the sampling rate and the while loop.

How to Use STM32 ADC in DMA Mode (HAL)

Using DMA for a single channel does not provide much benefit. The interrupt still triggers at the same rate as it does in interrupt mode, so there is no noticeable advantage. However, for completeness, we will also cover the DMA mode in this tutorial.

CubeMX Configuration for Interrupt Mode

We will see the available configuration for all three MCUs i.e F103C8, F446RE and H750VB.

STM32F446RE ADC DMA Configuration

The image below shows the ADC DMA configuration for STM32F446RE.

STM32F446RE ADC DMA mode  Configuration for single channel

In the first image above, you can see that we need to enable Continuous Conversion Mode. This makes sure each new conversion starts right after the previous one, so the process runs continuously and the interrupt triggers at a steady rate. For a single channel, this ensures the same channel is converted without interruption.

On the F446RE, you will also find an option to enable DMA Continuous Request. With this enabled, the DMA can keep transferring data without needing to be restarted manually.

Next, go to the DMA settings and add a request for the ADC. Set the mode to Circular, so the DMA automatically re-triggers after each transfer. If you select Normal mode, the DMA stops after one transfer, and you must start it again.

Finally, if the ADC resolution is higher than 8 bits, set the Data Width to Half Word (16 bits).


STM32F103C8 ADC DMA Configuration

The image below shows the ADC DMA configuration for the STM32F103C8.

STM32F103 ADC DMA mode Configuration for single channel

As shown in the image above, we need to enable Continuous Conversion Mode. This ensures that each new conversion starts right after the previous one finishes, so the process keeps running and the interrupt triggers at a steady rate. For a single channel, this makes sure the same channel is converted continuously.

Unlike the F446RE, the F103C8 does not provide an option to enable DMA Continuous Request. Since this feature is missing, we simply configure the DMA in Circular mode, which automatically allows DMA to request data transfers continuously.

In the DMA settings, add a request for the ADC and make sure the mode is set to Circular. This lets DMA re-trigger itself after each transfer. If you use Normal mode, the transfer will stop after one cycle, and you will need to restart it manually.

Finally, if the ADC resolution is higher than 8 bits, set the Data Width to Half Word (16 bits).


STM32H750 ADC DMA Configuration

The image below shows the ADC DMA configuration for the STM32H750VB.

STM32H750VB ADC DMA mode Configuration for single channel

As shown in the image above, we need to enable Continuous Conversion Mode. This makes sure that each conversion starts right after the previous one finishes, so the process runs continuously and the interrupt triggers at a steady rate. For a single channel, this ensures that the same channel is converted without stopping.

Here we also have a new option called Conversion Data Management Mode. This setting lets us decide what to do with the converted data. We can either keep the result in the ADC Data Register (DR) or use DMA to transfer it automatically. In this tutorial, we will use DMA Circular Mode, which allows DMA to continuously move the converted data without manual intervention.

Next, open the DMA settings and add a request for the ADC. Be sure to set the DMA mode to Circular so that it can re-trigger automatically after each transfer. If you choose Normal mode, the transfer stops after one cycle, and you would need to restart it manually.

Finally, if the ADC resolution is higher than 8 bits, set the Data Width to Half Word (16 bits).


STM32 ADC DMA Mode HAL Code

The code will remain the same for all the STM32 MCUs. If there are any changes, I will specify in this section itself.

Definitions

Let’s define some variables, which we will use in the main function later.

uint16_t ADC_VAL = 0;
int value = 0;

long map(long x, long in_min, long in_max, long out_min, long out_max)
{
  return (x - in_min) * (out_max - out_min + 1) / (in_max - in_min + 1) + out_min;
}

We define ADC_VAL as a 16-bit variable, and we use it to store the converted ADC value. Even if we select 10-bit, 12-bit, or 14-bit resolution, we still declare ADC_VAL as a 16-bit variable.

We will use the map function to map the converted ADC value to our desired range. The mapped value will be stored in the value variable, which I have defined as an integer here.


The main function

Below is the main function.

int main ()
{
  ....
  ....
  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)ADC_VAL, 1);
  while (1)
  {
  }
}

Inside the main function, we will start the ADC in the DMA mode. The parameter ADC_VAL is the array where we want to store the converted data, and 1 is the number of conversions we want the DMA to transfer.

Once the DMA finishes the transfer of the converted data from the channel, an interrupt will trigger and the Conversion complete callback will be called. We will write the rest of the code inside this callback function.


ADC Conversion Complete Callback

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
	  value = map(ADC_VAL[0], 1700, 65535, 0, 100);
}

Unlike the interrupt mode, Inside the callback we do not need to use the function HAL_ADC_GetValue. The data is already stored in the ADC_VAL array, therefore we can simply process this data.

We now have the converted data stored in the ADC_VAL variable. We will use the map function to map the converted value to 0 to 100 range. Since I have set the ADC resolution to 16bits, the maximum value of the ADC_VAL variable will be 216-1 = 65535. Similarly, the maximum value for the 12bits resolution will be 212-1 = 4095 and the same for the 10bits resolution will be 210-1 = 1023.

I have set the minimum input value to the map function as 1700. This is because it is the value we were getting when the slider was at the extreme left. Similarly the maximum value to the input of the map function will be 65535, as this is the highest value for the 16 bit resolution.


Fixing DMA Cache Coherency on STM32 Cortex-M7 (MPU Setup)

In this section, we will look at the configuration and code required specifically for Cortex-M7 devices.

ST recommends enabling both the Instruction cache and Data cache to improve system performance. However, when these caches are active, DMA often runs into cache coherency issues. To handle this problem, we use the Memory Protection Unit (MPU) available on Cortex-M7, which ensures proper data consistency between cache and memory.

The image below shows an example of the MPU configuration for Cortex-M7 devices.

STM32 CortexM7 MPU Configuration for ADC DMA

As you can see in the image above, I have enabled both Instruction and Data Cache (ICache & DCache).

We also need to enable the MPU in the MPU_PRIVILEGED_DEFAULT mode. Here I am choosing the Base Address region 0x30000000, this is the region for the RAM_D2 and we will relocate our buffer in this region.

  • Here I have selected the 32 Bytes region, since it’s the least size available. The ADC Buffer is going to be 20 bytes in size.
  • The rest of the configuration is to set the region as non-cacheable region. This would prevent the cache coherency issue between the CPU and the DMA.

Inside the main file, we need to relocate the ADC_VAL array to the RAM_D2 Region, 0x30000000. We can do this by using the section attribute as shown below. Here I am relocating the ADC_VAL array to the section “.adcarray“.

__attribute__((section(".adcarray"))) uint16_t ADC_VAL[10];

The section (.adcarray) will be defined in the flash script file as shown below.

  .mysection (NOLOAD):
  {
    . = ABSOLUTE(0x30000000);
    *(.adcarray)
  } >RAM_D2

As you can see above, the section (.adcarray) is linked to the region RAM_D2 (0x30000000).

The rest of the code remains the same as we used in the DMA section.


Result of ADC in DMA Mode

The gif below shows the ADC values obtained by moving the potentiometer, in the STM32CubeIDE debugger.

Debugging STM32 ADC single channel DMA mode in CubeIDE with potentiometer input connected on development board

As you can see in the gif above, the first element of the ADC_VAL array is updating continuously. The value variable also varies with the slider.

This means that the DMA is continuously transferring the data, and it is working fine even with the cache being enabled. There is no issue of the cache coherency, otherwise the data in the ADC_VAL array would not update at all.

STM32 ADC Interrupt & DMA Mode — Video Tutorial

Watch the complete walkthrough of this tutorial — configuring single channel ADC in Interrupt and DMA modes in CubeMX for STM32F103, F446, and H750, writing the HAL code, handling the conversion complete callback, setting up circular DMA, and verifying results in the STM32CubeIDE debugger.

Download STM32 ADC Interrupt & DMA Mode Project Files

Complete CubeMX projects for STM32F103, STM32F446, and STM32H750, covering single channel ADC in both Interrupt and DMA circular mode, including the Cortex-M7 MPU configuration for cache coherency. Free to download — support the work if it helped you.

F103 + F446 + H750 Interrupt + DMA Circular CubeMX + HAL source

STM32 ADC Interrupt & DMA Mode — Frequently Asked Questions

Conclusion

In this tutorial, we covered two more efficient alternatives to polling for reading a single ADC channel on STM32 — Interrupt mode and DMA mode. We walked through the CubeMX configuration for all three boards (F103, F446, and H750), wrote the HAL code for both modes, and handled the conversion complete callback in each case. For the H750, we also covered the MPU setup needed to prevent cache coherency issues when using DMA on Cortex-M7 devices.

In the next tutorial, we will move on to reading multiple ADC channels using DMA in Normal mode, where the ADC scans through a group of channels and transfers all results in a single DMA pass.

Browse More STM32 ADC Tutorials

1 2
About the Author
Arun Rawat
Arun Rawat
Embedded Systems Engineer · Founder, ControllersTech

Arun is an embedded systems engineer with 10+ years of experience in STM32, ESP32, and AVR microcontrollers. He created ControllersTech to share practical tutorials on embedded software, HAL drivers, RTOS, and hardware design — grounded in real industrial automation experience.

Subscribe
Notify of

1 Comment
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Alok
2 months ago

I needed to add HAL_ADC_Start_IT(&hadc1) again in ADC Callback function to continuously get data, otherwise ADC is stopping after 1 conversion even though I have enabled continuous conversion in STM32F401RE. Same case with DMA I have to start ADC by HAL_ADC_Start_DMA in callback function

×

Don’t Miss Future STM32 Tutorials

Join thousands of developers getting free guides, code examples, and updates.