How to Read Multiple Channels with DMA Circular Mode
This is the Fourth tutorial in the STM32 ADC series. In this series will see how to use the ADC peripheral of the STM32 to read the data from the Analog devices. We will cover how to use the ADC in different modes, that includes polling mode, interrupt mode and the DMA mode. We will also see how to use the multiple channels to read multiple analog devices using the same ADC. Later in the series we will cover more advanced features like differential ADC, combined ADCs, ADC watchdogs, injected channels etc.
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 will cover how to use the ADC Multiple channels in the DMA mode. We will use the DMA in Circular mode, so that it will automatically fetch the updated channel data from the ADC. We just need to start the ADC in DMA mode once, and then it will continue forever fetching the data from ADC channels.
Configuration
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.
F103C8
We will start with the F103C8. Below is the image showing the configuration for the it.
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.
The F103C8 has 12 bit ADC resolution 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).
F446RE
Below is the image showing the configuration for the F446RE.
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).
H750VB
Below is the image showing the configuration for the F446RE.
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 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.
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“. 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.
Connection
Below is the image showing the connection between the potentiometers and the H750.
As you can see in the image above, the 4 potentiometers are connected to the 4 pins of the MCU. These 4 pins represents 4 channels of the ADC. The potentiometers are connected to the 3.3V from the MCU.
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[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. Even if you are using 10bits, 12bits or 14bits resolution the ADC_VAL will still be defined as a 16 bit variable.
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.
Once the DMA finishes the transfer of the converted data from all the channels, an interrupt will trigger and the Conversion complete callback will be called. The DMA will again restart the transfer request from the ADC, and this process will continue forever.
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
You can check the video below to see the entire working.