STM32 ADC#1. Single Channel Polling Mode
This is the first 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 configure the ADC and How to use the polling mode to read the data from a single potentoometer.
STM32F103 Configuration
Below is the image showing the clock configuration for the STM32F103C8T6.
The system is clocked by the external 8MHz crystal and we can use the PLL to run the system at maximum 72 MHz. Further the ADC prescaler (6 in the image) can be used to set the ADC clock (12MHz in the image) at desired frequency.
The STM32F103C8T6 supports the ADC clock upto a maximum frequency of 14MHz.
Below is the image showing the ADC Configuration.
I have selected the ADC1 Channel 0 and you can see the pin PA0 is configured as the ADC1_IN0 pin. The ADC is configured in the independent mode. In this mode, the ADC1 can be used independently and we do not need any other ADC for it.
Resolution
The STM32F103C8T6 ADC does not have the option to configure the ADC Resolution. By default the Resolution is 12 bit and it is fixed. You can find this information in the reference manual of the MCU. The image below is from pg 215 of the STM32F103C8 reference manual.
Data Alignement
Since the ADC Resolution is 12bit, the data is stored in a 16 bit Data Register. The Data Alignment controls whether we want to align the data to the left or right of this array. Below is the image showing the data alignment.
We will keep the Data alignment to the Right, as it is easier to process the data.
Scan Conversion mode
This mode is used to scan a group of analog channels. This mode will be automatically selected if you are converting more than 1 channel. A single conversion is performed for each channel of the group. After each end of conversion the next channel of the group is converted automatically. If the continuousconversionmode is ENABLED, conversion does not stop at the last selected group channel but continues again from the first selected group channel.
Since we are using single channel in this tutorial, we will keep this mode disabled.
Continuous conversion mode
In continuous conversion mode ADC starts another conversion as soon as it finishes one. This method is more efficient if you want to convert continuously. In case of single channel, the same channel will be converted again and again.
We are using the polling mode in this tutorial, where we will manually call the ADC to start the conversion. So we will keep this disabled for this tutorial.
Regular/Injected Conversion
The ADC can be used in either the Regular conversion Mode or injected conversion mode. The Regular conversion mode is simple as it sounds, basically the normal ADC mode we use. Whereas in the injected mode, we can ‘inject’ a sequence of conversions in between the regular conversions. The injected channels has higher priority, so the ADC will pause the conversion for the regular sequence, then first convert the injected sequence, and it will resume the regular sequence afterwards.
External Trigger Source
The Trigger Source can be used to start the conversion. We can use the timer’s output to trigger the ADC conversion, and hence we can also control the conversion rate. For now we will set this trigger to be launched by the software, so that we can manually start the conversion when needed.
Rank
The Rank section is used to configure the individual channel. When using multiple channels, we can configure the sequence of conversion here. Also we can set the sampling time for each channel. The sampling time is set in ADC CYCLES, and the higher the cycles, the more time ADC will take to sample each channel.
I am setting the maximum sampling time as here we are using a simple potentiometer and time requirement is not an issue.
STM32F446 Configuration
Below is the image showing the clock configuration for the STM32F446RE.
The system is clocked by the external 8MHz crystal and we can use the PLL to run the system at maximum 180 MHz. There is no separate ADC Clock shown in the clock configuration.
If we check the block diagram at pg 16 of the STM32F446RE Datasheet, we can see that the ADC1 is connected to the APB2 Clock. The image is shown below.
As per our current clock configuration, the APB2 clock is at 90MHz.
Below is the image showing the ADC configuration.
I have selected the ADC1 Channel 0 and you can see the pin PA0 is configured as the ADC1_IN0 pin. The ADC is configured in the independent mode. In this mode, the ADC1 can be used independently and we do not need any other ADC for it.
Prescaler
We have the option to configure the clock Prescaler for the ADC. Our current APB2 clock is at 90MHz, and if we set the prescaler to 6, the ADC clock will be reduced to 90/6 = 15MHz.
Resolution
The F446RE has the option to configure the ADC Resolution. We can set the Resolution to 6its, 8bits, 10bits or 12bits. The higher the resolution is, the more accurate will be the ADC data, but the ADC will take more time to sample and convert the result.
Here I have configured the resolution to 12bits.
The rest of the configuration is similar to what we have seen in the F103C8. We have the Data alignment, Scan conversion mode, continuous conversion mode, regular conversion, rank, sampling time etc. So I will cover the ones which are different here.
DMA Continuous Request
This feature is used along with the DMA. Basically while using the DMA to fetch the data from the ADC, we can enable this feature to allow the DMA to continuously request the data from the ADC. This way we do not need to call the DMA again after it has finished receiving the data.
End of Conversion Section
The end of conversion flag (EOC) defines that the conversion is finished and the data is ready to be processed. We can control whether this flag should set after each channel is converted, or after a sequence of channels are converted (in case of multiple channels).
Since we are using a single channel in this tutorial, either of the options are fine. But you need to be careful while using the multiple channels. This flag should be configured as according to how you want to the process the data. For example, if you are converting 10 channels and set the EOC for each channel, the interrupt will trigger 10 times for each sequence.
STM32H750 Configuration
Below is the image showing the clock configuration for STM32H750.
The system is clocked by the external 25MHz crystal and we can use the PLL to run the system at maximum 480 MHz. The ADC is clocked by the PLL2p, which can be configured separately. With the current configuration, the ADC clock is at 15MHz.
Below is the image showing the ADC configuration.
I have selected ADC1 Channel 3 in the Single Ended Mode. The single Ended mode is the Normal ADC mode we have available in the above mentioned MCUs also. In this mode, the ADC measures the voltage on the input pin. Other than this, we have another option to configure the ADC in the Differential mode. Where the ADC will measure the voltage different between the two inputs.
We will cover the differential mode in the future tutorials.
Prescaler
We have the option to configure the clock Prescaler for the ADC. Our current ADC clock is at 15MHz, and if we set the prescaler to 10, the ADC clock will be reduced to 15/10 = 1.5MHz.
Resolution
The H750 has the option to configure the ADC Resolution as well. We can set the Resolution to 8bits, 10bits, 12bits, 14bits or 16bits. The higher the resolution is, the more accurate will be the ADC data, but the ADC will take more time to sample and convert the result.
Here I have configured the resolution to 16bits.
The rest of the configuration is similar to what we have seen in the F446RE. We have the Scan conversion mode, continuous conversion mode, End of Conversion, regular conversion, rank, sampling time etc. So I will cover the ones which are different here.
Left Bit Shift
The Left Bit Shift (LSHIFT) is a way to shift the converted value towards the left. The result may be negative, so the output data is signed. We will keep it disabled for now and might cover it in the future tutorials.
Converted Data Management Mode
The mode specifies what to do with the converted data. We can transfer the converted data using the DMA, or transfer the data to the DFSDM Register (Digital Filter for Sigma-Delta Modulator) or leave the data in the ADC DR (Data Register).
We are not using the DMA transfer right now, so we will leave the data in the DR (Data Register).
Oversampling
We will not cover the oversampling for now, so ignore any configuration related to that. We will cover it in the future tutorials.
Connection
Below is the image showing the connection between STM32H750 and the Potentiometer.
As you can see above:
- The VCC from the Potentiometer is connected to the 3.3V.
- The Gnd is connected to the Gnd.
- The Out pin is connected to the pin PA6 (ADC1_INP3).
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 = 0;
int value = 0;
long map(long x, long in_min, long in_max, long out_min, long out_max)
{
return (x - in_min) * (out_max - out_min + 1) / (in_max - in_min + 1) + out_min;
}
ADC_VAL is a 16 bit variable, which will be used 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.
We will use the map function to map the converted ADC value to our desired range. The mapped value will be stored in the value variable, which I have defined as an integer here.
The main function
Below is the main funciton.
int main()
{
.....
.....
while (1)
{
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 100);
ADC_VAL = HAL_ADC_GetValue(&hadc1);
HAL_ADC_Stop(&hadc1);
value = map(ADC_VAL, 0, 65535, 0, 100);
HAL_Delay (500);
}
}
We want to measure the ADC continuously, so we will write our code in the while loop. Below are the steps to get the ADC data in polling method.
- First START the ADC.
- Then Poll for the conversion to complete. Here I have set the timeout to 100ms. If the ADC does not finish the conversion within this time, the function will timeout.
- Then Read the converted value and store it in the ADC_VAL variable.
- Finally STOP the ADC.
We now have the converted data stored in the ADC_VAL variable. We will use the map function to map the converted value to 0 to 100 range. Since I have set the ADC resolution to 16bits, the maximum value of the ADC_VAL variable will be 216-1 = 65535. Similarly, the maximum value for the 12bits resolution will be 212-1 = 4095 and the same for the 10bits resolution will be 210-1 = 1023.
The while loop will run every 500ms, therefore we will keep getting the updated data.
Result
Below is the gif showing the result in the STM32CubeIDE debugger.
You can see when the slider is moved towards the right, the ADC_VAL is increasing towards 65535 and the value variable is increasing towards 100. Similarly, when the slider is moved towards the left, the ADC_VAL is reduced and the value variable is reduced towards 0.
You can watch the video to see the complete working of the process explained above.