Master STM32 ADC reading on multiple channels without DMA. This tutorial includes CubeMX setup and HAL C code for STM32F1, F4, and H7 to read channels individually. The project is available to download at the end of the post.

Recommended Resources:
This is the 5th tutorial in the STM32 ADC series. In the previous tutorials we covered how to configure the ADC in STM32 F1, F4 and F7 series and how to use it in the single channel polling, interrupt and DMA modes to read the potentiometer data. We have also covered the Multiple channels in DMA Normal Mode and Circular Mode.
You should take a look at the following tutorials before continuing here:
- STM32 ADC Single Channel Polling Mode
- STM32 ADC Single‑Channel Interrupt & DMA Modes
- STM32 ADC Multi-Channel DMA (Normal Mode)
- STM32 ADC Multi‑Channel Circular DMA Mode
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.
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 Configuration
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 Configuration
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 Configuration
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.
WIRING DIAGRAM
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.
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.