How to Read Multiple Channels with DMA Normal Mode
This is the Third 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 Normal mode, so that it will automatically stop after transferring the data only once. This way we can call the DMA only when we want to convert the 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 Normal mode, so it will only transfer the data once.
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 disabled. Doing so will make sure the ADC does not start another conversion for all the channels automatically. This way the ADC will only convert the data when we want it to do.
In the DMA section, add the DMA request for the ADC1. Also make sure that the DMA is configured in the Normal Mode. In this mode the DMA will not transfer the data continuously, rather it will transfer once and then stop.
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 disabled. Doing so will make sure the ADC does not start another conversion for all the channels automatically. This way the ADC will only convert the data when we want it to do.
The DMA Continuous Request must also be disabled. This is to make sure that the DMA does not request the data continuously from the ADC. This mode should be kept disabled while using the DMA in Normal 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 Normal Mode. In this mode the DMA will not transfer the data continuously, rather it will transfer once and then stop. 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 disabled. Doing so will make sure the ADC does not start another conversion for all the channels automatically. This way the ADC will only convert the data when we want it to do.
The Conversion Data Management Mode should be set to DMA One Shot. This is to make sure that the DMA does not request the data continuously from the ADC. This mode should be used while using the DMA in Normal 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 Normal Mode. In this mode the DMA will not transfer the data continuously, rather it will transfer once and then stop. Set the data width as per the ADC Resolution. 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 isADCFinished = 0;
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 variable isADCFinished will be used to determine whether the ADC conversion has been finished.
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);
if (isADCFinished == 1)
{
//process data
isADCFinished = 0;
HAL_ADC_Start_DMA(&hadc1, ADC_VAL, 4);
}
}
}
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.
Conversion Complete Callback
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
isADCFinished = 1;
}
Inside the callback we will set the variable isADCFinished. This will indicate that the DMA has finished the transfer and we can now process the data anywhere in the code.
The DMA will stop after transferring the data for all the channel once. Therefore we need to start the ADC in DMA mode whenever we want to convert the ADC data.
Inside the while loop we will check the state of the variable isADCFinished. If this variable is set, we will process the converted data and then start the ADC in DMA mode again. Here I am starting the ADC again, just to show the working for the sake of the tutorial. You can start the ADC when you want to fetch the converted data from the ADC.
Result
You can check the video below to see the entire working.