Last Updated: April 16, 2026
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 Polling Mode
- Multiple Channels using Normal DMA Mode
- Multiple Channels using Circular DMA Mode
- Multiple Channels Without DMA | Full Control

- STM32 ADC Potentiometer Wiring and Pin Connections
- STM32 ADC Interrupt vs DMA Mode: Key Differences
- How to Use STM32 ADC in Interrupt Mode (HAL)
- How to Use STM32 ADC in DMA Mode (HAL)
- STM32 ADC Interrupt & DMA Mode — Video Tutorial
- Download STM32 ADC Interrupt & DMA Mode Project Files
- STM32 ADC Interrupt & DMA Mode — Frequently Asked Questions
STM32 ADC Potentiometer Wiring and Pin Connections
The image below shows the connection between STM32H750 and the Potentiometer.
The pin connection is described in the table below.
| Potentiometer Pin | Connected To |
|---|---|
| VCC | 3.3V on STM32 |
| GND | GND on STM32 |
| OUT | PA6 (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?
| Feature | Interrupt Mode | DMA Mode |
|---|---|---|
| CPU Involvement | CPU reads data via ISR on each conversion | CPU is completely free; DMA handles transfers |
| Efficiency | Moderate — suitable for infrequent sampling | High — ideal for continuous or high-speed data |
| Complexity | Simple to set up | Requires additional DMA and buffer configuration |
| Latency | Small delay due to interrupt handling | Very low — transfers are handled in hardware |
| Best Use Case | Occasional or event-based ADC readings | High-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.
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.
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.
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.
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.
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.
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_D2As 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.
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.
STM32 ADC Interrupt & DMA Mode — Frequently Asked Questions
Yes, you can combine them. The interrupt can notify you when the DMA buffer is filled, allowing efficient data handling without continuous CPU intervention.
The buffer size depends on how much data you want to capture before processing. A larger buffer reduces interrupt frequency but increases memory usage.
This usually happens due to cache coherency issues (especially on Cortex-M7 devices). Using MPU configuration or disabling the cache for the DMA region fixes it.
Not always. DMA is ideal for continuous or high-speed sampling, but for occasional readings, interrupt mode is simpler and often sufficient.
Yes, circular mode works with multiple channels as well. The DMA continuously fills the buffer with data from all selected channels in sequence.
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
STM32 ADC Multiple Channels DMA Normal Mode – CubeMX & HAL Code
STM32 ADC Multiple Channels with Circular DMA: CubeMX Setup & HAL Code
STM32 ADC Multiple Channels Without DMA: CubeMX Setup & HAL Code
STM32 ADC Conversion Time: Formula, Calculation & Frequency Explained
STM32 ADC External Trigger with Timer: CubeMX & HAL Guide
STM32 ADC Injected Conversion Mode: Triggered & Auto Injection
STM32 ADC Hardware Oversampling: Improve Resolution & Reduce Noise
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.
Recommended Tools
Essential dev tools
Categories
Browse by platform










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