STM32 ADC | PART 2 Single‑Channel Interrupt & DMA using HAL

Discover how to set up STM32 ADC1 for single‑channel analog-to-digital conversion using interrupt and DMA modes. The tutorial Includes CubeMX config and tested HAL C code. The project is available to download at the end of the post.

STM32 ADC Single‑Channel Interrupt & DMA

Recommended Resources:

This is the 2nd tutorial in the STM32 ADC series. In the previous tutorial we covered how to configure the ADC in STM32 F1, F4 and F7 series and how to use it in the single channel polling mode to read the potentiometer data.
This tutorial is a continuation of the previous one, so the configuration remains the same.

You should take a look at the following tutorials before continuing here:

Introducing STM32 ADC Peripheral

The Analog-to-Digital Converter (ADC) in STM32 microcontrollers is a powerful peripheral that allows the conversion of analog input signals into digital values. This capability enables STM32 devices to interface seamlessly with real-world sensors and analog inputs, such as temperature sensors, light sensors, potentiometers, and more. The ADC module is highly configurable and supports a range of resolutions and modes, making it suitable for both basic and advanced embedded applications.

Whether you’re developing a battery-powered IoT device or a high-speed data acquisition system, the STM32 ADC provides flexible configuration options, precision control, and efficient performance.

Here are some important features of STM32 ADC:

  • Multi-Channel Support – Capable of reading from multiple analog inputs using internal channel scanning, reducing the need for external hardware.
  • High Resolution Options – Offers selectable resolutions (6-, 8-, 10-, or 12-bit), allowing a balance between accuracy and conversion speed.
  • Multiple Conversion Modes – Supports single, continuous, scan, and discontinuous modes to suit different application needs.
  • Flexible Triggering – Conversion can be started via software or automatically triggered by hardware events like timers or external interrupts.
  • Low Power Consumption – Designed with power-saving features, ideal for energy-efficient and battery-operated systems.

USE ADC IN INTERRUPT MODE

CubeMX Configuration

Below is the image showing the cubeMX configuration for the Single channel Interrupt Mode.

ADC Interrupt Configuration
ADC Interrupt Configuration

As you can see in the first image above, we need to enable the continuous conversion mode. This is to make sure that the second conversion should start as soon as the first one is finished. This way the conversion will keep taking place continuously, and the interrupt will trigger at a constant rate. In our case of single conversion, enabling the continuous conversion will make sure the the conversion for the same channel will take place continuously.

In the NVIC tab, make sure to enable the ADC global interrupt.

The 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;
}

ADC_VAL is a 16 bit variable, which will be used to store the converted ADC Value. Even if you are using 10bits, 12bits or 14bits resolution the ADC_VAL will still be defined 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.

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

Below is the gif showing the result of the above code.

ADC Interrupt Mode Working

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.

USE ADC IN DMA MODE

Using DMA for a single channel is of no use. The interrupt will trigger at the same rate as that in the case of the interrupt mode, so basically we don’t have any noticeable advantage by using the DMA. But just for the sake of it, we will cover the DMA mode as well.

CubeMX Configuration

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

F446RE

We will start with the F446RE. Below is the image showing the configuration for the it.

F446RE ADC Configuration

As you can see in the first image above, we need to enable the continuous conversion mode. This is to make sure that the second conversion should start as soon as the first one is finished. This way the conversion will keep taking place continuously, and the interrupt will trigger at a constant rate. In our case of single conversion, enabling the continuous conversion will make sure the the conversion for the same channel will take place continuously.

The F446RE has an option to enable the DMA Continuous Request. This mode ensures that the DMA can make a continuous data transfer request to the ADC and hence the we do not need to start the DMA again.

In the DMA settings, add the DMA request for the ADC. Make sure the DMA mode is circular, so that the DMA can re-trigger the request automatically after the required data has been transferred once. In Normal mode the DMA will stop after the required data has been transferred, and we need to start the transfer request again for a new transfer.

If the ADC resolution is higher than 8bits, set the Data Width as Half Word (16bits).

F103C8

Below is the image showing the configuration for the F103C8.

F103 ADC Configuration

As you can see in the image above, we need to enable the continuous conversion mode. This is to make sure that the second conversion should start as soon as the first one is finished. This way the conversion will keep taking place continuously, and the interrupt will trigger at a constant rate. In our case of single conversion, enabling the continuous conversion will make sure the the conversion for the same channel will take place continuously.

Unlike F446RE, the F103C8 does not has the option to enable the DMA Continuous Request. Since the option is not available, we will simply set the DMA in the circular mode and this will automatically enable the DMA to make the continuous data requests.

In the DMA settings, add the DMA request for the ADC. Make sure the DMA mode is circular, so that the DMA can re-trigger the request automatically after the required data has been transferred once. In Normal mode the DMA will stop after the required data has been transferred, and we need to start the transfer request again for a new transfer.

If the ADC resolution is higher than 8bits, set the Data Width as Half Word (16bits).

H750VB

Below is the image showing the configuration available for the H750VB.

H750VB ADC Configuration

As you can see in the image above, we need to enable the continuous conversion mode. This is to make sure that the second conversion should start as soon as the first one is finished. This way the conversion will keep taking place continuously, and the interrupt will trigger at a constant rate. In our case of single conversion, enabling the continuous conversion will make sure the the conversion for the same channel will take place continuously.

Here we have anew option for Conversion Data Management Mode. It allows us to configure what to do with the converted data. We can leave the data in the ADC Data Register (DR), or we can use DMA to transfer the data. Here I am using the option DMA CIRCULAR MODE, which will allow the DMA to continuously transfer the converted data.

In the DMA settings, add the DMA request for the ADC. Make sure the DMA mode is circular, so that the DMA can re-trigger the request automatically after the required data has been transferred once. In Normal mode the DMA will stop after the required data has been transferred, and we need to start the transfer request again for a new transfer.

If the ADC resolution is higher than 8bits, set the Data Width as Half Word (16bits

The 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[10];
int count = 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;
}

ADC_VAL is an array of 10 elements of 16 bit in size. We will use this array to store the converted ADC Value. Even if you are using 10bits, 12bits or 14bits resolution the ADC_VAL will still be defined 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.

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.

Cortex M7 Related

In this section we will cover the configuration and code exclusively needed for the cortex M7 devices.

We know that as per ST’s recommendation, we should use the Instruction and Data cache to improve the system performance. But if the cache are enabled, the DMA will always have the cache coherency issue. This is why we will use the Memory Processing Unit (MPU) of the cortex M7 to deal with the cache coherency.

Below is the image showing the MPU configuration in the cortex M7 Devices.

CortexM7 MPU Configuration 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.

In 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.

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

Here I am relocating the ADC_VAL array to the section “.adcarray“. 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

Below is the gif showing the output of the DMA code.

ADC Working in DMA Mode

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.

VIDEO TUTORIAL

You can check the video to see the complete explanation and working of this project.

Check out the Video Below

PROJECT DOWNLOAD

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

You May Also Like..

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments

🛈 Advertising Disclosure

This website relies on advertisements as its main source of revenue to support the creation of free, high-quality content.
If you enjoy our work and would like to help us continue, please consider disabling your ad blocker while browsing here.

Your support truly makes a difference — thank you!

Share this Article

Recent Posts

Join Our Community

Weekly Newsletter

Subscribe to our newsletter to get our news