How to Read Multiple Channels without DMA
This is the Fifth 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 read the ADC Multiple channels without using the DMA mode. We will write functions to convert the individual channel so that we can convert any channel we want at any point in the code.
Configuration
We will connect 4 potentiometers to the 4 channels of the same ADC. As I mentioned, we will then fetch the data from any channel we want.
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. We want to convert only 1 channel at a time, and also we want the channel to only convert when we initiate it with the command. Therefore this mode should be disabled.
Here despite using the multiple channels, we are not using the DMA.
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. We want to convert only 1 channel at a time, and also we want the channel to only convert when we initiate it with the command. Therefore this mode should be disabled.
The DMA Continuous Request must also be Disabled. Since we are not using DMA, this mode is of no use.
The End of Conversion Selection should be set at the end of single channel. Doing this will make sure that the Conversion complete flag is set after each channel is converted. Since we are converting 1 channel at a time, this is an important parameter.
Here despite using the multiple channels, we are not using the DMA.
H750VB
Below is the image showing the configuration for the H750.
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. We want to convert only 1 channel at a time, and also we want the channel to only convert when we initiate it with the command. Therefore this mode should be disabled.
The Conversion Data Management Mode should be set to Data Register (DR).
The End of Conversion Selection should be set at the end of single channel. Doing this will make sure that the Conversion complete flag is set after each channel is converted. Since we are converting 1 channel at a time, this is an important parameter.
Here despite using the multiple channels, we are not using the DMA.
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];
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.
Channel Functions
We will now define functions to convert the ADC channels. Since we are converting 4 channels, we will define 4 functions, 1 function for each channel.
uint16_t ADC_Convert_Rank1 (void)
{
/* Configure channel */
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
sConfig.OffsetSignedSaturation = DISABLE;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* Convert the Channel */
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
uint16_t adcval = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
return adcval;
}
The function ADC_Convert_Rank1 is used to convert the Rank 1 i.e Channel 3. The channel configuration is copied from the ADC initialisation function, which is generated by the cubeMX.
Note that the channel 3 is configured with Rank 1 and the sampling time is as we configured in the cubeMX.
We will use the blocking mode to convert the channel. Since we have configured the conversion complete flag to set after each channel, once this particular channel is converted the flag will set and we will get the result.
Below is the function to convert the Rank 2.
uint16_t ADC_Convert_Rank2 (void)
{
ADC_ChannelConfTypeDef sConfig = {0};
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_387CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
sConfig.OffsetSignedSaturation = DISABLE;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
uint16_t adcval = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
return adcval;
}
As per the cubeMX configuration, Rank 2 is assigned to channel 4. Therefore the function ADC_Convert_Rank2 is used to convert Channel 4.
Note that we will still configure the channel 4 with rank 1. This is because we want to convert a single channel at a time, therefore all the channels must have the rank 1.
Similarly, we will define separate functions for channel 7 and channel 8, where both the channels should be configured with rank 1.
This way we can convert whichever channel we want. We do not need to convert all the channels just to get the data for 1 channel.
The main function
As I mentioned, we can convert any channel at any point in our code. Just for the demonstration, I will convert the channels inside the while loop as shown below.
int main ()
{
....
....
while (1)
{
ADC_VAL[0] = ADC_Convert_Rank1();
ADC_VAL[1] = ADC_Convert_Rank2();
ADC_VAL[2] = ADC_Convert_Rank3();
ADC_VAL[3] = ADC_Convert_Rank4();
HAL_Delay(250);
}
}
Here inside the while loop I am converting all the channels and their data is being stored in the ADC_VAL array. We can also convert a single channel whenever we want.
Result
ou can check the video below to see the entire working.