STM32F0 ADC - Tutorial 6
It’s been nearly one year since I posted the last tutorial on using STM32F0 UART. Many things have happened during my study that gave me no chance to release any new tutorial. But I finally finished my MSc course and had some free time so I make up my mind to go back and continue with what I am interested in here instead of watching cats’ and dogs’ videos on Youtube :-P. In this post, I’ll talk about STM32F0 ADC.
Specifically, I will cover almost every part of Analog-to-Digital Converter (ADC) module of STM32F051 on STM32F0 Discovery board using HAL library with support from STM32CubeMX. This is applicable for other low-level STM32 series as their ADC module are quite similar. I will try to cover: Fundamental knowledge of ADC; Single channel/sequence conversion, continuous and discontinuous ADC conversion with/without interrupt.
Two examples will be given: I will first try to measure the supply voltage for the STM32F0 chip, which is also the reference voltage for ADC module. Then, an example of reading the chip’s temperature using built-in temperature sensor as well as reading external analog voltage applied to the analog input pin (PA1) will be made. To view the ADC conversion data and other internal variables, I will use STM Studio which has been introduced in this post.
Analog to Digital Converter – Things to notice
STM32F0 ADC resolution
ADC resolution is one of the key factors to determine how precise the conversion can achieve. I still remember the first time I made a circuit to convert analog to a digital signal using ADC0804 chip. Such a chip has a resolution of 8-bit (0-255), meaning that we can detect 256 different levels of input analog signal. At that time, 12-bit (4096), 16-bit (65536) or a higher bit of resolution was something that I always wanted to have for my application because it’s very hard and expensive to find those dedicated ADC chips with that high resolution. Then Atmel and Microchip came out with built-in 10-bit ADC (1024 levels) microcontrollers that shrink both size of PCB and the complexity of circuit design.
Now, we have our STM32 chip that supports 12-bit ADC, which increases the resolution of analog conversion to 4096 steps (from 0 to 4095). This resolution is configurable to 12-bit, 10-bit, 8-bit or 6-bit where faster conversion times can be obtained by lowering the resolution. In this tut, I will pay attention to 12-bit resolution only to exploit the full potential of the STM32 chip.
Reference Voltage of STM32F0
For low level, less number of pins series of STM32 such as STM32F051 in the STM32F0 Discovery Kit, the reference voltage (VREFINT) of ADC module is fixed to internal voltage reference provided by internal power block. This reference voltage is equal to the supply voltage to the Vdd pin, which is around 3V for this STM32F0 Discovery kit.
On the other hand, the conversion data of VREFINT at precisely 3.3V is individually measured for each chip by ST during the production tests and stored in the system memory area. This will help us in the calculation of ADC value when the supply voltage for the STM32 is different from 3.3V (3V for specific), which will be mentioned later.
Sampling time and Conversion time
The sampling time is the time needed to charge up all the capacitors for sampling purposes inside the ADC module. This sampling time must be enough for the input voltage source to charge the sample and hold the capacitor to the input voltage level. Therefore, choosing this sampling time will mostly depend on the input resistance of the input voltage source, the lower the resistance, the lower the sampling time and vice versa.
The duration of 1 cycle shown in the figure above depends on the clock frequency of the ADC module. The ADC clock has two options: asynchronous clock (at 14MHz) which is independent of the CPU clock and the synchronous clock which depends on the running frequency of the chip. Option 1 has the advantage of reaching the maximum ADC clock frequency whatever the APB clock scheme selected. Option 2 is useful when the application requires that the ADC is precisely triggered without any uncertainty.
The total conversion time for one channel is calculated as follows:
tCONV = (Sampling time + 12.5) * ADC clock cycles
STM32 single-channel ADC conversion with interrupt
In this part, I will show how to perform a single channel AD conversion by using the internal reference channel (ADC_IN17). Please take a look at the video below to get a rough idea of how it is done.
STM32F0 uses internal reference voltage (VREFINT). This internal voltage is connected to ADC_IN17 (channel 17) and can be measured. Moreover, the precise voltage of VREFINT is individually measured for each part by ST during the production test and stored in the system memory area with respect to supply voltage Vdd = 3.3V. This means that we can calculate the input analog voltage precisely in spite of not knowing the exact Vdd voltage. This is different from Arduino (Mega328) where we only know the internal reference voltage is 2.5V but cannot measure precisely. The following formula gives the actual Vdd voltage supplying the device:
\[Vdd=3.3*{VREFINT_{CAL} \over VREFINT_{DATA}}\]
where VREFINT_CAL is the VREFINT calibration value, which is stored in the system memory; VREFINT_DATA is the actual VREFINT output value converted by ADC.
The ADC delivers a digital value corresponding to the ratio between the analog power supply and the voltage applied on the converted channel. For most application use cases, it is necessary to convert this ratio into a voltage independent of Vdd:
\[V_{channel}=Vdd*{ADC_{Data} \over 4095}\]
The ADC interrupt callback in the video is fairly simple:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (__HAL_ADC_GET_FLAG(hadc, ADC_FLAG_EOC))
{
ADC_raw = HAL_ADC_GetValue(hadc);
Vdd = 3300 * (*VREFINT_CAL_ADDR)/ADC_raw;
}
}
Continuous mode for the single-channel conversion
With continuous mode disabled for single channel conversion, we need to trigger the start of conversion manually by using the HAL function:
HAL_ADC_Start_IT(&hadc);
An illustration below shows how the process is going when the continuous mode is disabled:
With continuous mode enabled for single channel conversion, the ADC module automatically restarts the conversion process right after the previous conversion is done. In this way, the interrupt will be triggered continuously with the rate equal to the total conversion time calculated in the section above.
STM32 sequence ADC conversion with multiple channels (channel scanning) with interrupt
In this part, a demonstration of how to perform ADC on multiple channels will be presented. STM32 has an internal temperature sensor so that it can measure the temperature of the microcontroller itself. The temperature sensor is connected to ADC_IN16 (channel 16). In this STM32F0 ADC tutorial, I will use this internal temperature sensor as our analog input for all the experiments. We will use one external input (ADC_IN1), the internal temperature as a second channel (ADC_IN16) and Vdd voltage (ADC_IN17) as the third channel. Let’s take a look at the following video first:
For external input channels, we have:
\[ADC_{Data}=V_{in}*{Resolution - 1 \over V_{ref}}=V_{in}*{4096 - 1 \over V_{ref}}\]
where:
- ADC_Data is the digital output from the conversion
- Vref is the reference voltage which is the Vdd
- Vin is the input analog voltage to convert. This voltage must always be lower or equal to Vref so as to avoid saturation problem
- Resolution: 4096 for STM32 (we use full 12-bit)
If you need to calculate input voltage from the converted digital output, the equation above can be rewritten as:
\[V_{in}=Vdd*{ADC_{Data} \over 4095}\]
The code for ADC interrupt callback in the video for ADC_IN1, Temperature and Vdd is as:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if (__HAL_ADC_GET_FLAG(hadc, ADC_FLAG_EOC))
{
ADC_raw[index] = HAL_ADC_GetValue(hadc);
index++;
}
if (__HAL_ADC_GET_FLAG(hadc, ADC_FLAG_EOS))
{
index=0;
Vdd = 3300 * (*VREFINT_CAL_ADDR) / ADC_raw[2];
temperature = (((int32_t)ADC_raw[1] * Vdd/3300)- (int32_t) *TEMP30_CAL_ADDR );
temperature = temperature * (int32_t)(110 - 30);
temperature = temperature / (int32_t)(*TEMP110_CAL_ADDR - *TEMP30_CAL_ADDR);
temperature = temperature + 30; Vin = Vdd*ADC_raw[0]/4095;
}
}
With TEMP110_CAL_ADDR and TEMP30_CAL_ADDR can be found from the datasheet:
Continuous mode and Discontinuous mode for the sequence conversion
When we are having multiple channels for our ADC module, the discontinuous mode is needed to be taken into account. The following illustrations present the differences among various configurations for continuous mode and discontinuous mode:
With both modes disabled: the conversion sequence starts from the first channel until the last channel and then stops. Each time the channel conversion is done, the End of Conversion (EOC) interrupt flag will be triggered until the last channel, End of sequence (EOS) is on.
With continuous mode enabled, and discontinuous mode disabled: similarly to single channel, the conversion process will be restarted automatically when the last channel is finished.
With continuous mode disabled, and discontinuous mode enabled: Every time the channel conversion is done, we need to trigger the start of ADC module again to move on to the next channel in the sequence.
I hope you like it and stay tuned for the next tutorial, which will be about I2C.