HomeSTM32STM32 ADC SeriesSTM32 ADC Part 4 – Multiple Channels with DMA Circular Mode

STM32 ADC Multiple Channels using Circular DMA

In this part of the STM32 ADC tutorial series, we will learn how to read data from multiple channels using DMA Circular Mode. Unlike Normal Mode, Circular DMA continuously transfers the converted values into memory without stopping, making it ideal for applications that require real-time and continuous data acquisition. This approach reduces CPU overhead since the DMA automatically restarts after completing a sequence. We will configure the ADC in CubeMX, generate the code, and test the results on STM32 boards.

This is the 3rd tutorial in the STM32 ADC series. I am going to use the STM32H750 based development board for this series because it has advanced ADC features. But I will also cover the configuration changes required for some of the popular series that includes STM32F103C8 and STM32F446RE.
This tutorial is a continuation of the previous one, so the configuration remains the same. This is the continuation of PART1 and PART2, so you must go read them first.

STM32 ADC Multiple Channels Circular DMA Video Tutorial

This tutorial shows how to configure STM32 ADC to read multiple channels using DMA in Circular Mode. You’ll learn how to set the number of conversions, enable scan mode, configure DMA in Circular mode, and process the results efficiently. To make things easier, I’ve created a complete video walkthrough that covers the configuration, coding, and real-time output. Follow the written steps here while watching the video for a clearer understanding.

Watch the Video

Hardware Required for STM32 ADC DMA Tutorial

To follow this tutorial, you will need the following hardware:

  • STM32F446 Dev Board (e.g., STM32F4, STM32F7, STM32H7, or any board with ADC support)
  • Potentiometer (to provide an analog input signal)
  • Breadboard (for easy circuit connections)
  • Jumper Wires (male-to-male for connections)
  • USB Cable (to connect the STM32 board to the PC for programming and debugging)

STM32 ADC Multiple Channels Wiring Diagram

The image below shows the connection between STM32H750 and the multiple Potentiometers.

Multiple Potentiometers connected to different ADC channels in STM32.

As shown in the image above, four potentiometers are connected to four pins of the MCU. All potentiometers share the same 3.3V supply and GND from the MCU. Only the wiper (signal pin) of each potentiometer is connected to a unique ADC-capable pin on the STM32.

The pin connection is described in the table below.

PotentiometerPin 1 (VCC)Pin 2 (Signal / Wiper)Pin 3 (GND)MCU Pin (ADC Channel)
Potentiometer 13.3VMiddle Pin → Pink WireGNDPC5 (ADC Channel 15)
Potentiometer 23.3VMiddle Pin → Orange WireGNDPC4 (ADC Channel 14)
Potentiometer 33.3VMiddle Pin → Blue WireGNDPA6 (ADC Channel 6)
Potentiometer 43.3VMiddle Pin → Green WireGNDPA6 (ADC Channel 6)

What is Multi-Channel DMA Normal Mode?

In STM32, the ADC (Analog-to-Digital Converter) can sample multiple channels (e.g., potentiometers, sensors, or input signals). To move the conversion results from the ADC peripheral to memory efficiently, we use DMA (Direct Memory Access).

In Multi-Channel DMA Circular Mode, the ADC samples several channels in sequence, and the DMA transfers each conversion result into memory (typically an array).

Once the DMA finishes storing the configured sequence, instead of stopping, it automatically loops back and starts writing new data at the beginning of the buffer. This creates a continuous stream of updated ADC values without any CPU intervention.

At any point, the CPU can access the buffer to process the most recent values.

Why use Circular Mode vs Normal / Interrupt?

  • Circular Mode continuously refreshes the buffer with the latest ADC data, making it perfect for real-time applications where uninterrupted sampling is required.
  • Normal Mode stops after one complete sequence, requiring the CPU or software to restart it, which is more suited for periodic sampling.
  • Interrupt Mode doesn’t use DMA. The CPU handles each conversion individually, which increases overhead and is less efficient for multiple channels.

Advantages of Circular Mode

  • Provides continuous sampling without manual restarts.
  • Offloads the CPU, since DMA handles all transfers automatically.
  • Ensures that the buffer always contains the most up-to-date conversion results.
  • Ideal for high-speed, real-time applications.

Limitations of Circular Mode

  • Data Overwrite: Old values are replaced with new ones, so if the CPU doesn’t read in time, some data may be lost.
  • Slightly more complex to handle compared to Normal Mode.
  • May require synchronization logic to ensure the CPU reads consistent data.

Best Use Cases for Circular Mode

  • Real-time sensor monitoring (e.g., temperature, pressure, or motion sensors).
  • Motor control applications where continuous feedback is essential.
  • Audio signal sampling or waveform analysis.
  • Any application where uninterrupted, high-speed data acquisition is required.

ADC Multi-Channel Modes Comparison

ModeHow it WorksAdvantagesLimitationsBest Use Cases
DMA NormalADC samples multiple channels → DMA stores results in memory → stops automatically after one sequence.– Simple and efficient for batch conversions- No buffer overwrite- Low CPU load– DMA must be restarted for next sequence- Not continuousSnapshot readings of multiple sensors, periodic measurements
DMA CircularADC samples continuously → DMA keeps overwriting buffer in a loop.– Continuous, real-time data- Low CPU load- No manual restart needed– Data can be overwritten if CPU doesn’t read fast enough- Harder to capture “exact” snapshotContinuous sensor monitoring, audio processing, streaming data
InterruptADC signals CPU after each conversion → CPU reads data manually.– Easy to implement- Fine control over each conversion– High CPU overhead- Not efficient for multiple channels- Slower response at high sampling ratesSimple applications, single-channel ADC, low-speed sensing

CubeMX Configuration for Multiple Channels (Cicular DMA)

We will connect 4 potentiometers to the 4 channels of the same ADC. As I mentioned we will setup the DMA in Circular mode, so it will continue to transfer the data from ADC channels.

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

STM32F103C8 ADC DMA Configuration

The image below shows the configuration for STM32F103C8 ADC multiple channels using DMA Normal Mode.

STM32F103 ADC Multiple channel  DMA Configuration (Circular Mode)

I have enabled 4 channels of the ADC1. This is where the 4 potentiometers will be connected to.

Since we are using 4 different Channels of ADC1, set the Number of Conversions (under ADC_Regular_ConversionMode) to 4. This will automatically enable the Scan Conversion Mode, which is necessary in case of Multiple Channels.

You can configure the Rank and Sampling Time for each channel. The Rank determines the sequence in which the channels will be converted.

The Continuous Conversion Mode should be Enabled. Doing so will make sure the ADC start another conversion for all the channels automatically. This way the ADC will keep converting the data continuously.

In the DMA section, add the DMA request for the ADC1. Also make sure that the DMA is configured in the Circular Mode. In this mode the DMA will transfer the data continuously.

on by default and we don’t have the option to configure it. Therefore the data width for the DMA must be set to Half-Word (16 bits).


STM32F446 ADC DMA Configuration

The image below shows the configuration for STM32F446RE ADC multiple channels using DMA Normal Mode.

STM32F4 ADC Multiple channel  DMA Configuration (Circular Mode)

I have enabled 4 channels of the ADC1. This is where the 4 potentiometers will be connected to.

Since we are using 4 different Channels of ADC1, set the Number of Conversions (under ADC_Regular_ConversionMode) to 4. This will automatically enable the Scan Conversion Mode, which is necessary in case of Multiple Channels.

You can configure the Rank and Sampling Time for each channel. The Rank determines the sequence in which the channels will be converted.

The Continuous Conversion Mode should be Enabled. Doing so will make sure the ADC start another conversion for all the channels automatically. This way the ADC will keep converting the data continuously.

The DMA Continuous Request must also be Enabled. This is to make sure that the DMA requests the data continuously from the ADC. This mode should be kept Enabled while using the DMA in Circular mode.

The End of Conversion Selection should be set at the end of all the conversions. Doing this will make sure that the Conversion complete interrupt in only triggered when all the 4 channels (one complete sequence) have been converted.

In the DMA section, add the DMA request for the ADC1. Also make sure that the DMA is configured in the Circular Mode. In this mode the DMA will transfer the data continuously. Set the data width as per the ADC Resolution. I have configured the ADC with 12bits resolution, therefore the data width is set to half word (16bits).


STM32H7 ADC DMA Configuration

The image below shows the configuration for STM32F446RE ADC multiple channels using DMA Normal Mode.

STM32H7 ADC Multi-channel DMA Configuration (Circular Mode)

I have enabled 4 channels of the ADC1. This is where the 4 potentiometers will be connected to.

Since we are using 4 different Channels of ADC1, set the Number of Conversions (under ADC_Regular_ConversionMode) to 4. This will automatically enable the Scan Conversion Mode, which is necessary in case of Multiple Channels.

You can configure the Rank and Sampling Time for each channel. The Rank determines the sequence in which the channels will be converted.

The Continuous Conversion Mode should be Enabled. Doing so will make sure the ADC start another conversion for all the channels automatically. This way the ADC will keep converting the data continuously.

The Conversion Data Management Mode should be set to DMA Circular. This is to make sure that the DMA does requests the data continuously from the ADC. This mode should be used while using the DMA in Circular mode.

The End of Conversion Selection should be set at the end of sequence of conversion. Doing this will make sure that the Conversion complete interrupt in only triggered when all the 4 channels (one complete sequence) have been converted.

In the DMA section, add the DMA request for the ADC1. Also make sure that the DMA is configured in the Circular Mode. In this mode the DMA will transfer the data continuously. I have configured the ADC resolution of 16bits, therefore the data width is set to Half-Word (16bits).


Cortex-M7 Specific Configuration for ADC with DMA

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.

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.

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[4];

Here I am relocating the ADC_VAL array to the section “.adcarray“.

Next, define the section (.adcarray) 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.

STM32 HAL Code for ADC Multiple Channels

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[4];
//__attribute__((section(".adcarray"))) uint16_t ADC_VAL[4];  // cortex M7 only
int count = 0;

ADC_VAL is an array of 4 elements of 16 bit in size. We will use this array to store the converted ADC Value. The ADC always defines ADC_VAL as a 16-bit variable, even when you use 10-bit, 12-bit, or 14-bit resolution.

The count variable will keep track if the rest of the while loop is working fine.


The main function

Below is the main function.

int main ()
{
  ....
  ....
  HAL_ADC_Start_DMA(&hadc1, ADC_VAL, 4);
  while (1)
  {
    count++;
    HAL_Delay(500);
  }
}

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 4 is the number of conversions we want the DMA to transfer.

After the DMA transfers the converted data from all channels, it triggers an interrupt that calls the Conversion Complete callback. The DMA will again restart the transfer request from the ADC, and this process will continue forever.


ADC Conversion Complete Callback

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
  // Process the data
}

Inside the callback we can process the received data or set another variable. This variable indicate that the DMA has finished the transfer and we can now process the data anywhere in the code.

In circular mode, the DMA will continue transferring the data for all the channels. Therefore we do not need to start the ADC in DMA mode again.

Result of ADC Multiple Channels DMA

The video below shows the ADC values obtained by rotating the potentiometers, in the STM32CubeIDE debugger.

As you can see in the video, the element of the ADC_VAL buffer is changing with the rotation of the respective potentiometer. The data is not updating every 500ms.

The DMA transfers the data when the while loop calls it (every 500 ms), and it works fine even with the cache enabled. There is no issue of the cache coherency, otherwise the data in the ADC_VAL array would not update at all.

Conclusion

In this tutorial, we explored how to configure STM32 ADC for Multi-Channel DMA Circular Mode. Unlike Normal Mode, Circular DMA allows the ADC to continuously refresh a memory buffer with new conversion results, making it ideal for applications that require real-time and uninterrupted data acquisition. This approach not only reduces CPU load but also ensures that the latest values from all channels are always available for processing.

While Circular DMA is powerful for continuous sampling, it may not always be necessary. In some cases, you may prefer to avoid DMA altogether and directly handle ADC conversions through software.

In the next part of this series, we’ll learn how to read multiple ADC channels without using DMA, giving you a better understanding of when to use DMA and when a simpler approach is sufficient.

Next Tutorials in the STM32 ADC Series

1 2

PROJECT DOWNLOAD

Info

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

FAQs for STM32 ADC Multiple Channels Circular DMA

Subscribe
Notify of

3 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Kyle
1 month ago

Hello, I’m trying to use the STM32H753ZI nucleo board with two ADG732 mux.. one mux connected to ADC1_INP2 and one mux connected to ADC1_INP3 for a total of 64 input channels. I need to scan both ADC channels, step mux address, and repeat, filling a ring buffer[64]. I put my DMA buffer in RAM_D3 as this area is a non-cacheable area, so I didnt need to mess with MPU/cache. However, its not working as intended. I can either get all 64 buffer spots to continuously show channel0 of their respective mux (buffer[0,2,4,6,8…] showing mux1 channel0 and buffer[1,3,5,7,9….] showing mux2 channel0), or they cycle through all 32 channels of their respective mux (buffer[0,2,4,6,8…] cycling through mux1 channel0,2,4,6,8… and buffer[1,3,5,7,9…] cycling through mux2 channel1,3,5,7,9…). I have not been able to get it to fill the buffer correctly (each buffer spot remains with its intended channel i.e. buffer[0] = mux1 channel0, buffer[1] = mux2 channel0, buffer[2] = mux1 channel1, and so on. Can you please help?

Vatsal
3 months ago

Hi, I am using same setup and code you have suggested for 446RE but when there is any change in one channel then it is affecting others as well. I am using sampling time for all 480 cycles. Any suggestions?

Amir
5 months ago

Awesome