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 having some free time so I make up my mind to go back and continue with what I am interested 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 discontinuos 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 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.

1. 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 digital signal using ADC0804 chip. Such 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 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 complexity of circuit design.

Fig.: Difference in digital output corresponding to difference resolution. Obviously, higher resolution represents the sampled analog input more precisely. source:

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 full potential of 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 supply voltage to 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 production test and stored in the system memory area. This will help us in 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 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.

STM32 ADC sampling time

STM32 ADC sampling time

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 with 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 (edited):

tCONV = (Sampling time + 12.5) * ADC clock cycles

2. 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 on 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 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:

where:  VREFINTCAL is the VREFINT calibration value, which is stored in the system memory;  VREFINTDATA is the actual VREFINT output value converted by ADC.

VREFINT_CAL memory address

VREFINT_CAL memory address

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:

The ADC interrupt callback in the video is fairly simple:

Continuous mode explanation 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:

An illustration below shows how the process is going when continuous mode is disabled:

Illustration of single channel, continuous mode disabled analog to digital conversion process.

Illustration of single channel, continuous mode disabled analog to digital conversion process.

With continuous mode enabled for single channel conversion, the ADC module automatically restart 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 section above.

Illustration of single channel, continuous mode enabled analog to digital conversion process.

Illustration of single channel, continuous mode enabled analog to digital conversion process.

3. STM32 sequence ADC conversion with multiple channels (channel scanning) with interrupt

In this part, a demonstration on how to perform ADC on multiple channels will be presented. STM32 has internal temperature sensor so that it can measure 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 channel, we have:


  • ADCData is the digital output from 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

The code for ADC interrupt callback in the video for ADC_IN1, Temperature and Vdd is as:

With TEMP110_CAL_ADDR and TEMP30_CAL_ADDR can be found form the datasheet:

STM32F0 Internal temperature calibration addresses

STM32F0 Internal temperature calibration addresses

Continuous mode and Discontinuous mode explanation 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 configuration for continuous mode and discontinuous mode:

With both modes disabled: the conversion sequence start from the first channel until the last channel and then stop. 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) will be on.

Illustration of 2 channels, continuous mode disabled, and discontinuous mode disabled analog to digital conversion process.

Illustration of 2 channels, continuous mode disabled, and discontinuous mode disabled analog to digital conversion process.

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.

Illustration of 2 channels, continuous mode enabled, and discontinuous mode disabled analog to digital conversion process.

Illustration of 2 channels, continuous mode enabled, and discontinuous mode disabled analog to digital conversion process.

With continuous mode disabled, and discontinuous mode enabled: Everytime 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.

Illustration of 2 channels, continuous mode disabled, and discontinuous mode enabled analog to digital conversion process.

Illustration of 2 channels, continuous mode disabled, and discontinuous mode enabled analog to digital conversion process.

I hope you like it and stay tuned for the next tutorial, which will be about I2C.




25 Thoughts on “STM32F0 ADC – Tutorial 6

  1. Dimitry Kloper on 25/07/2016 at 9:33 PM said:

    May I suggest that it is actually

    Vdd = 3.3V * Vrefint_DATA / Vrefint_CAL

    or am I missing something?

    • Le Tan Phuc on 27/07/2016 at 8:46 PM said:

      Hi, thank you for you comment. I’ve checked again, according to STM32F0 reference manual, page 253, it states that Vdd = 3.3V * VREFINT_CAL/VREFINT_DATA.

      • Dimitry Kloper on 27/07/2016 at 8:54 PM said:

        You are absolutely right. Thanks.
        Another question. I see that Vrefint varies randomly in range of (0x620, 0x680) is this normal? Or my Vdd is too unstable?

        • Le Tan Phuc on 14/08/2016 at 6:49 PM said:

          hi, I also faced that problem and I think it’s normal. You can improve by adding coupling capacitor. Another way, if it’s not necessary, you can try using moving average filter for VREFINT or just read it for the first time running.

  2. TahaOzden on 25/07/2016 at 10:54 PM said:

    You are missed ! I am looking forward for you to finish this tutorial. Best wishes with your life :=)

  3. Mohammad on 26/07/2016 at 8:42 AM said:

    Thank u so much, continue …

  4. Fabrizio Dutto on 26/08/2016 at 2:53 AM said:

    Very interesting subjects on your site!! Keep up the very good job!

  5. Nathan on 30/08/2016 at 8:16 AM said:

    Hello, and welcome back 🙂
    IN the last few months I’ve been working more on the STM32, and have learned a few things, such as operating the MPU6000 IMU in full SPI mode with DMA and interrupts, as well as using a µSD card with 4-bit mode to transfer/save data. Maybe we could compare notes?
    I am interested in getting the ADC working with a timer interrupt: my supervisor wants me to include pressure transducers on my next hypersonic model; because the STM32 is also handling the full control loops, gyro, µSD, and wifi transmissions, I need to limit the sampling rate to 200kHz, since the amplifiers I’m designing will be low-pass filtered at 100kHz.
    So far I haven’t had any luck getting the ADC to sample at 200kHz, although it works fine in DMA mode at higher speeds.
    I’ll look forward to seeing your continuation of this tutorial, since I see you will include interrupts.
    Thanks again.

    • Le Tan Phuc on 03/09/2016 at 11:25 AM said:

      Hi Nathan, DMA is the best choice for sure but if you want to use interrupt, you can try playing around with the sampling cycle, clock mode, continuous mode of the ADC configuration and the most important thing is making sure that all the interrupt routines must be performed as fast as possible. As you mentioned that your STM32 has already been busy with all the gyro, wifi transmission things, just need to make sure all the calculation should be done in the main loop, interrupt routines are just for getting the data out from peripherals.

  6. Nathan on 06/09/2016 at 9:32 AM said:

    Hello again,
    Thank you for uploading the last parts of the tutorial, it helped me to get 2 channels to simultaneously transfer with interrupts, although interestingly, the F407VGT chip doesn’t seem to have the option for the asynchronous clock, or the ADC_FLAG_EOS.

    I managed to get the ADC to work on DMA, and also forced the DMA to work on a timer interrupt. This is how I managed to get the IMU to work at high speed (only 6µs to do a full conversion). For the IMU, I set it to only run the DMA once, but then called the HAL_DMA_Start within the interrupt service routine.

    I tried the same procedure with the ADC, by using a timer set to 1 second: it was set for scan conversion mode enabled (since I was checking more than one channel), but continuous conversion disabled, and DMA continuous requests enabled. The DMA was set to circular mode, with a ‘word’ data width.
    Basically, I copied this tutorial:
    but with the continuous conversion disabled, and timer3 enabled.

    Then in my software, I have:

    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){
    if (htim-> Instance == htim3.Instance)
    if (HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC1ConvertedValues, 2) != HAL_OK) ;


    This works, although it seems very slow: if I left the system clock at the standard 84MHz, it would jam. It wasn’t until it was increased to 168MHz that it ran, and even then if I increased the ADC1ConvertedValues buffer to 10 or so, I could see in STM Studio the values loading in sequentially, which I wouldn’t have thought likely with the DMA. It was almost as though the ADC/DMA clock itself had dropped to the 1 second reload value from timer3.

    Maybe using the DMA inside a timer interrupt callback like this causes problems with the ADC/DMA timer?

  7. ahmet on 22/10/2016 at 4:20 AM said:

    hi Mr. Le Tan Phuc thanks for tutorials ..
    I write code on Keil
    for example declaration variable uint_16 i;
    but it not show i stmstudio . thanks for your comment.

  8. Ha Diep on 07/11/2016 at 5:02 PM said:

    Chào a,
    Cho e hỏi hơi lạc đề xíu, e đang tìm cách truyền data 16-bit qua SPI (kể cả DMA) để e fill màu cho màn hình ILI9341 của em. E dùng thư viên HAL (CubeMX) của ST, nhưng vẫn ko biết cách truyền, a thử dùng truyền như thế chưa a?

  9. Fabio on 10/11/2016 at 4:12 PM said:

    Hi Mr. Le Tan Phuc thanks a lot for tutorials..

    Can you help me? I don’t understand your formula:

    Tconv = Sampling time + 12.5 * ADC clock cycles

    “Tconv” is expressed in s
    “Sampling time” is expressed in cycles (so I think it’s adimesional)
    “ADC clock cycles” is expressed in Hz

    Where is my mistake?

    • Fabio on 10/11/2016 at 6:05 PM said:

      Ok I found it by myself.
      “Tconv” is expressed in s
      “Sampling time” is expressed in s (ex. 1.5 Cycles = 1.5 / fADC)
      “ADC clock cycles” is expressed in s (1 / fADC)

  10. Fabio on 11/11/2016 at 4:06 PM said:

    Hi, I think there is a refuse:

    temperature = (((int32_t)ADC_raw[1] * Vdd/3300)- (int32_t) *TEMP30_CAL_ADDR );

    temperature = ( (int32_t)ADC_raw[1] – (int32_t) *TEMP30_CAL_ADDR );

  11. Nguyễn Huy Dũng on 13/11/2016 at 4:44 PM said:

    em đang đọc manual con f0 trang 237 có cách tính Tconv mà em không hiểu mong anh giải thích giúp em với!
    làm sao để tính ra Tconv = 1µs? theo công thức thì phải là
    Tconv = 1.5 + 12.5*14 = ….? chứ anh!
    ” The total conversion time is calculated as follows:
    tCONV= Sampling time + 12.5 x ADC clock cycles
    With ADC_CLK = 14 MHz and a sampling time of 1.5 ADC clock cycles:
    tCONV= 1.5 + 12.5 = 14 ADC clock cycles = 1 µs “

    • Le Tan Phuc on 16/11/2016 at 4:59 PM said:

      Hi em, anh check lai roi, dung ra la phai co dau ngoac cho 1.5+12.5=14. Cong thuc dung la (1.5+12.5) ADC clock cycles thi se ra 1us.

      • Nguyễn Huy Dũng on 16/11/2016 at 10:31 PM said:

        Trong Video1 có file MXconstants.h là file tự tao hay sao vạy anh?

  12. Hi Le Tan Phuc,
    how are you able to continuously read adc values without restarting the adc, when not using continuous mode?

    • Le Tan Phuc on 04/01/2017 at 11:13 PM said:

      hi, if you are not using continuous mode, you need to trigger the conversion again by using “HAL_ADC_Start(&hadc1)” right after you read the data.

      • Yes, or actually HAL_ADC_Start_IT(&hadc1); ?

        But you did not choose continuous mode in CubeMX in the video, were you supposed to?

        • Le Tan Phuc on 18/01/2017 at 5:19 PM said:

          Oh, in the video, I manually triggered the ADC every 200ms. You can notice that I put the HAL_ADC_Start_IT(&hadc1) inside the main loop together with 200ms delay. So yes, I didn’t use the continuous mode in the video. For the previous comment, it should be HAL_ADC_Start_IT(&hadc1) if you use interrupt and HAL_ADC_Start(&hadc1) if you use polling method.

Post Navigation