Simple ADC use on the STM32

Here is a bit of a look at how to use the ADC on the STM32 for simple applications. The ADC peripheral on the STM32 processor is a flexible but complex beast. The peripheral driver library should make it relatively easy to use. After all, there is no need to remember the names of all those pesky registers and bitfields. There are anything up to 18 multiplexed channels that can be converted singly, continually, scanned or discontinuously. The results can be transferred to memory through DMA and some devices have two or more ADCs for simultaneous conversions. With all those options, simple reading of an ADC port is a little tricky…

The first thing to note is that, on the 64 pin packages, Vref is not connected to a package pin but is connected internally to Vdda the analogue supply voltage. If you have one of the larger, 100 pin devices, Vref is tied to a pin.

My test setup is the ETT STM32 Stamp which has an 8MHz external crystal and runs the PLL to give a system clock of 72MHz.

The clock for the ADC is provided by a prescaler fed from PCLK2, the APB2 clock. By default, in the peripheral library, this is the same speed as the system clock so, on my board, that is 72MHz. According to the datasheet, the ADC clock should be in the range 600kHz to 14MHz so I will need to set a prescaler of 6 to bring the ADC clock within that range from the 72MHz system clock. the function that sets this is part of the RCC peripheral code (RCC_ADCCLKConfig()) rather than the ADC driver. The available divisors are 2, 4, 6 and 8 so that should present no problems. Running the ADC overspeed is possible but I would expect accuracy to suffer. Since the fastest conversion takes 12.5+1.5 = 14 cycles, I would expect to get about 857,000 conversions per second on my board. The maximum rate within the given limits is 1 million samples per second.

The master ADC (ADC1) has two additional channels. These are connected to the internal temperature sensor (ADC1_IN16) and the internal reference voltage (ADC1_IN17). the temperature sensor is said to be reliable in terms of the slope of its response but with an offset than can vary my as much as 40 degrees from chip to chip. For that reason, it is said to be better for measuring temperature change than absolute temperature. The internal voltage reference is not used by the ADC but can be used as a comparator for zero crossing detection. It may also be possible to use it to calibrate external readings or the Vref+ pin.

Once configured, the ADC can be turned on by setting the ADON bit in CR2. After a stabilisation period, the peripheral is ready to begin conversions. From the datasheet for the medium density devices, the stabilisation period will not exceed 1us.

The ADC can self-calibrate to reduce errors due to internal capacitor variations. the reference manual suggests that the ADC be self calibrated once after power up. Before starting the self calibration, the ADC must have been in the power-down state (ADON=0) for at least two clock cycles. the end of calibration is indicated by the CAL flag going low.

The result of a conversion may be optionally aligned left or right in the 16 bit result register. Only 12 bits of the result are significant. In regular mode, the other bits are filled with zeros.

Here is the configuration code I have used:

void ADC_Configuration(void)
  ADC_InitTypeDef  ADC_InitStructure;
  /* PCLK2 is the APB2 clock */
  /* ADCCLK = PCLK2/6 = 72/6 = 12MHz*/

  /* Enable ADC1 clock so that we can talk to it */
  RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
  /* Put everything back to power-on defaults */

  /* ADC1 Configuration ------------------------------------------------------*/
  /* ADC1 and ADC2 operate independently */
  ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
  /* Disable the scan conversion so we do one at a time */
  ADC_InitStructure.ADC_ScanConvMode = DISABLE;
  /* Don't do contimuous conversions - do them on demand */
  ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
  /* Start conversin by software, not an external trigger */
  ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
  /* Conversions are 12 bit - put them in the lower 12 bits of the result */
  ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
  /* Say how many channels would be used by the sequencer */
  ADC_InitStructure.ADC_NbrOfChannel = 1;

  /* Now do the setup */
  ADC_Init(ADC1, &ADC_InitStructure);
  /* Enable ADC1 */

  /* Enable ADC1 reset calibaration register */
  /* Check the end of ADC1 reset calibration register */
  /* Start ADC1 calibaration */
  /* Check the end of ADC1 calibration */

Single conversion mode is started in software by writing a 1 to the ADON bit in ADC_CR2 with the CONT bit set to zero. Note that the ADON bit will already be set to one when the ADC is powered up. Reading the ADON bit serves to tell the program that the ADC is powered up. To prevent false triggering, a conversion will not start if any bit other than ADON is changed at the same time as ADON.

Conversions take 12.5 clock cycles but the time the ADC spend sampling the input can be varied on a channel-by-channel basis from 1.5 to 239.5 cycles. This value is set in the ADC_SMPR1 and ADC_SMPR2 registers.

The end of conversion is signalled by the setting of the EOC flag, an interrupt is optionally generated and the result is placed in the ADC_DR register. The EOC flag should be reset by software before another conversion is started. Reading the result from ADC_DR clears the EOC flag automatically.

Here is the function that reads a single ADC channel:

u16 readADC1(u8 channel)
  ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_1Cycles5);
  // Start the conversion
  ADC_SoftwareStartConvCmd(ADC1, ENABLE);
  // Wait until conversion completion
  while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
  // Get the conversion value
  return ADC_GetConversionValue(ADC1);

This function includes a call to configure the channel and sets the sample time to its minimum. It takes 3.5us to run on my board. That could be substantially reduced by using the more sophisticated conversion modes available and by using better code. The Channel config call alone takes 1.3us. Where high speed is essential, there is DMA and automatic sequencing available. However, if all you want to do is read a few ADC channels, the speed is pretty good. All 16 channels could be read in under 60us. Note that the sampling time may need adjusting according to the nature of the signal source. A good. low, impedance source will need a shorter sample time.

When I need the more clever methods of reading the ADC, I will look into them. In the meantime, this will do me very nicely.

Incoming search terms:

  • stm32 adc (174)
  • stm32f4 adc example (140)
  • stm32 adc example (131)
  • stm32f4 adc (63)
  • adc stm32 (56)
  • stm32f103 adc example (54)
  • stm32 adc dma example (49)
  • stm32f4 discovery adc example (33)
  • stm32 adc dma (27)
  • stm32 adc tutorial (23)
This entry was posted in STM32 and tagged . Bookmark the permalink.

79 Responses to Simple ADC use on the STM32

  1. sf says:

    Thanks for all the info you are sharing about stm32.

    I just discovered this beast last week, I’m playing around with my first board…
    Being used to PIC, it’s an huge step and you are helping me a lot!



  2. Jiangong XU says:

    Since the end of conversion is signalled by the setting of the EOC flag, so ADC_FLAG_EOC must be eaqual to 1;why use this
    expression “while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);:”in my opinion that expression may be changed to “while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == SET);”my view may be wrong ,please correct me,thanks a lot.

    Jiangong XU

  3. peteh says:

    The flag is reset when the conversion is started and will become set at the end of the conversion so the code sits and waits for the flag to become set, indicating that we can read the result.

  4. Jiangong XU says:

    Thanks for peteh‘s help,I know the reasons,the code“while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);”is used to wait for the flag to become set,when the flag(the end of conversion)is set,the code ”return ADC_GetConversionValue(ADC1);”is executed 。when the flag(the end of conversion)is reset,the program is doing nothing,but waiting for
    the end of conversion。
    thanks for friend ’s help。

  5. Jayesh says:

    Thank you very much for giving such a nice information regarding ADC in STM32.I am working on that. For any further information regarding other peripheral in STM32 letme inform.

    Thank you very much.

  6. Jayesh says:

    I want to make PID controller using STM32. In this i set the set value by keypad and measure the actual value by ADC and give the corrected output on DAC. I need PID logic for the same. please help me.

    Thank you

  7. peteh says:

    I cannot help with this at the moment. You would be better trying the ST forums:

  8. Manuel says:

    I would like to know how did you calculate the sample time of 3.5us, did you use timers to measure that value?. Please if you could help me doing that, i would be very grateful. I need to measure how much time does it takes to convert each channel.Thanks

  9. peteh says:

    I am afraid it was not calculated. I toggled an output pin before and after the function call and measured the time on an oscilloscope. The time taken is important to me because I have an application that needs to do 15 conversions in every 1ms control cycle.

  10. Manuel says:

    Hi, do you know how to use the end of conversion (EOC) interrupt?
    I need this to know where is the value of my last conversion. Thanks

  11. peteh says:

    So far, I have not used the ADC with interrupts – sorry.

  12. can i use this programe ,?
    how i know the pin that iam getting the informationg from it ?
    think you for the programe i really like it ,its simple and efficacy

  13. Adam says:

    Hi, is there any reason why the function readADC1() starts with line: ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_1Cycles5); ? Wouldn’t it be better (faster) to have this line in the function ADC_Configuration()? I exptect there is a good reason why you do it like this but I can’t figure out why.

    BTW: Thanks a lot, I’ve been visiting this site for inspiration every now and then. I appreciate your work.

  14. Adam says:

    Hi, is there any reason why the function readADC1() starts with line: ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_1Cycles5); ? Wouldn’t it be better (faster) to have this line in the function ADC_Configuration()? I exptect there is a good reason why you do it like this but I can’t figure out why.

    BTW: Thanks a lot, I’ve been visiting this site for inspiration every now and then. I appreciate your work.

  15. peteh says:

    I probably copied the example from somewhere else to be honest. There seems no reason this should not go into the config section. Thanks for noticing.

    Unfortunately, I have been unable to spend much quality time with the STM32 of late. 

  16. jrive says:

    I’m confused as to the programability of what ST calls sampling time. Is this akin to track and hold? Why wouldn’t you always sample as fast as it can? Is this because depending on the source impedance, you may need to allow more time for the hold capacitor to charge up? lower source impedance, faster samping time possible?


  17. Harjit Singh says:

    It is as you say – the sampling time needs to be varied to allow the sample/hold capacitor to charge up. They have an ADC app. note that walks through how to determine the sampling time.

  18. Johnson says:

    Would it be okay with you if I linked to this page from my website? Just asking since some people don’t allow linking to their sites if you don’t take their permission.

    Engineers who would like to have careers in management positions should seriously think about getting the PMP certification so that they can learn how to manage projects effectively. Getting the certification is a matter of passing the PMP exam which can be done with a bit of online PMP certification training.
  19. peteh says:

     Link away. Let me know the page you are linking from please.

  20. Llewellyn Remy says:


    Im trying to get a very simple system up and running, just measure an analog voltage, then use the variable in some calculaiton and turn some LED’s on and of with the info. I tried to copy/paste the code, but the line “ADC_Init(ADC1, &ADC_InitStructure);” gives me errors, (amp undeclared and unexpected ; or something)

    I it possible to point me in the right direction? How would i use this to read a analog signal of PC4 and save it as a variable? I’m running a STM32Discover, and TrueSTUDIO lite

  21. peteh says:

    Possibly the best way is to find one of the ADC examples that probably comes with Atollic. I have not used it so I cannot really say. You need to have al the correct header files included and the right variable defined.

    If you start with an existing ADC example and compare, then paste, you will stand a better chance.


  22. Llewellyn Remy says:

    Everything seems to be working(well, no errors or warnings. im just wondering, sorry if this is a silly question, but how would i call the function

    u16 readADC1(u8 channel)

    declare a int like
    u16 voltage1 = 0;

    the something like
    voltage1 = readADC();?

    Sorry if these are silly questions, before this ive only used the arduino, and that is substantially less comlpex than this :)


  23. peteh says:

    All you should need to do is add the channel number to the function call. 

    u16 temperature;
    temperature = readADC(3);

  24. Llewellyn Remy says:

    Thanks. Since everywhere in the code i used ADC1, this would then be:

    u16 voltage;
    voltage= readADC(1); ?

    also, I cant see where the program sets the pin it reads from. would this part i set here set it to read at pin PC4?

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
    GPIO_Init(GPIOC, &GPIO_InitStructure);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE);

  25. Llewellyn Remy says:

    Im reading the voltage n with

    ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    // Wait until conversion completion
    while(ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) == RESET);
    // Get the conversion value
    voltage = ADC_GetConversionValue(ADC1);
    vleds = voltage/1024;

    If the actual inpt is 3.2V, i read 3.0V, amd if i read actual 0V, it gives me 1V.

    Any suggestions?

  26. Anonymous says:

    Thank you for posting this info! Very helpful!

  27. Anonymous says:

    so just delete “amp;” and you’re good to go.

  28. Dennis says:

    Could you give me the code you wrote for your project?
    I would like to take a better look how you defined your functions etc. thats a bit mysterious to me for now.

    I’ll thank you very much for your effort to explain your project!
    It helps me alot!


  29. peteh says:

    All there was beside what you see is a main function that read the ADC and put the answer in a variable. That was run with the debugger so that I could take one sample and examine the result.

  30. Tony says:

    Just got the code below going with the DMA, its really cool as the information from the DMA goes straight into the pre-defined variable “ADCConvertedValue[2]“. The code below just turns an LED on or off depending on the condition of the voltage read on channel 14. Note the comments in my code are just an easy way of me understanding (or trying to understand) what the micro is doing; I do not guarantee that what I say in my comments is right. The code however works 100% :) The code is tailored to work on a ST Discovery board….

     *  Test code for the Discovery Board.  In this version we use the DMA to store the information
     *  of two converted channels (channel 14 and channel 15) into a pre-defined variable.  The method
     *  is continuous and the value of the ADC are continuously stored in the variable.
    #include "stm32f10x.h"
    #include "STM32vldiscovery.h"
    #define ADC1_DR_Address    ((uint32_t)0x4001244C)
    //==Global Variable declerations==
    uint16_t ADC_Val; //Stores the calculated ADC value
    double voltage1; //Used to store the actual voltage calculated by ADC for the 1st channel
    double voltage2; //Used to store the actual voltage calculated by ADC for the 2nd channel
    __IO uint16_t ADCConvertedValue[2]; //Array that is used to store the calculated DMA values for ADC1
     *  This function sets up the pins connected to the LED's as outputs; the blue LED
     *  is connected to pin 8 (Port C) and the green LED is connected to pin 9 (Port C).
    void Configure_LED_Pins() {
       //==Configure the pins connected to the LED's to be outputs==
       //Blue LED is on pin 8 of Port C and Green LED is on pin 9 of Port C
       //Enable the clock for the port, by default this is off i.e. Enable GPIOC Clock
       RCC->APB2ENR |= RCC_APB2Periph_GPIOC; //APB2 indicates we dealing with the high speed bus
       //ENR means we want to enable the register
       //Specify the pins as either inputs or outputs
       GPIOC->CRL = 0x11111111; //This is definition for pins 0 - 7; each pin is configured with respect to CONTROL:MODE
       //All pins set to output mode (general purpose output push-pull)
       GPIOC->CRH = 0x44444433; //This is definition for pins 8 - 15; pin 8 and 9 set as output (50Mhz), general purpose output
       //Set all pins to 0V
       GPIOC->ODR = 0x0000; //The output port only uses the first 16 bits; the last 16 isn't used...
       //Here we are turning off all the pins...
    }//end Configure_LED_Pins
     *  Sets up Pin.C4 (Channel 14) and Pin.C5 (Channel 15) to be used as analog inputs.  The first channel
     *  of the DMA is also setup to be used with ADC1.  ADC1 is setup to continuously output data to the
     *  array "ADCConvertedValue"
    void ADC_DMA_Configuration() {
       GPIO_InitTypeDef GPIO_InitStructure; //Variable used to setup the GPIO pins
       DMA_InitTypeDef DMA_InitStructure; //Variable used to setup the DMA
       ADC_InitTypeDef ADC_InitStructure; //Variable used to setup the ADC
       //==Configure the systems clocks for the ADC and DMA==
       //ADCCLK = PCLK2 / 4
       RCC_ADCCLKConfig(RCC_PCLK2_Div4); //Defines the ADC clock divider.  This clock is derived from the APB2 clock (PCLK2).  The
       //ADCs are clocked by the clock of the high speed domian (APB2) dibivied by 2/4/6/8.  The
       //frequency can never be bigger than 14MHz!!!!
       //--Enable DMA1 clock--
       RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
       //--Enable ADC1 and GPIOC--
       RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOC, ENABLE);
       //==Configure ADC pins (PC.04 -> Channel 14 and PC.05 -> Channel 15) as analog inputs==
       GPIO_StructInit(&GPIO_InitStructure); // Reset init structure, if not it can cause issues...
       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5;
       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
       GPIO_Init(GPIOC, &GPIO_InitStructure);
       //==Configure DMA1 - Channel1==
       DMA_DeInit(DMA1_Channel1); //Set DMA registers to default values
       DMA_InitStructure.DMA_PeripheralBaseAddr = ADC1_DR_Address; //Address of peripheral the DMA must map to
       DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) & ADCConvertedValue; //Variable to which ADC values will be stored
       DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
       DMA_InitStructure.DMA_BufferSize = 2; //Buffer size (2 because we using two channels)
       DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
       DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
       DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
       DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
       DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
       DMA_InitStructure.DMA_Priority = DMA_Priority_High;
       DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
       DMA_Init(DMA1_Channel1, &DMA_InitStructure); //Initialise the DMA
       DMA_Cmd(DMA1_Channel1, ENABLE); //Enable the DMA1 - Channel1
       //==Configure ADC1 - Channel 14 and Channel 15==
       ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
       ADC_InitStructure.ADC_ScanConvMode = ENABLE;
       ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
       ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
       ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
       ADC_InitStructure.ADC_NbrOfChannel = 2; //We using two channels
       ADC_Init(ADC1, &ADC_InitStructure); //Initialise ADC1
       //Setup order in which the Channels are sampled....
       ADC_RegularChannelConfig(ADC1, ADC_Channel_14, 1, ADC_SampleTime_55Cycles5);
       ADC_RegularChannelConfig(ADC1, ADC_Channel_15, 2, ADC_SampleTime_55Cycles5);
       ADC_DMACmd(ADC1, ENABLE); //Enable ADC1 DMA
       ADC_Cmd(ADC1, ENABLE); //Enable ADC1
       //==Calibrate ADC1==
       //Enable ADC1 reset calibaration register
       while (ADC_GetResetCalibrationStatus(ADC1)); //Check the end of ADC1 reset calibration register
       //Start ADC1 calibaration
       while (ADC_GetCalibrationStatus(ADC1)); //Check the end of ADC1 calibration
    }//end ADC_Configuration
     *  Simple program that reads our AD port, in this case pin 4 on port C.  The following program does the following:
     *  The converted voltage is stored in the global variable 'voltage'; the following things are done when the voltage value is
     *  obtained:
     *  0.000 < = V  No LEDs on
     *  0.825 <= V  Green LED on; Blue LED off
     *  1.650 <= V  Blue LED on; Green LED off
     *  2.475 <= V  Green LED on; Blue LED on
     *  Since the variable 'voltage' is a global variable, its value can be checked when debugging the code....
    void ADC_DMA_Test_Program() {
       /* Start ADC1 Software Conversion */
       ADC_SoftwareStartConvCmd(ADC1, ENABLE);
       while (1) {
          //==Get the ADC value of channel 14==
          ADC_Val = ADCConvertedValue[0];
          voltage1 = (2.984 * ADC_Val) / 4096;
          //==Get the ADC value of channel 15==
          ADC_Val = ADCConvertedValue[1];
          voltage2 = (2.984 * ADC_Val) / 4096;
          if ((voltage1 >= 0) && (voltage1 < 0.825)) {
             ODR = 0b0000000000000000; //Turn off both LED's
          } else if ((voltage1 >= 0.825) && (voltage1 < 1.65)) {
             ODR = 0b0000001000000000; //Turn on the green LED
          } else if ((voltage1 >= 1.65) && (voltage1 < 2.475)) {
             ODR = 0b0000000100000000; //Turn on the blue LED
          } else {//voltage1 >= 2.475 && voltage
             ODR = 0b0000001100000000; //Turn on both LED's
       }//end while(1)
    }//end ADC_Test_Program
     * Main program start
    int main(void) {
       //**Micro clock settings**
       //Done by default from startup_stm32f10x_xx.s before coming to main!  You can edit SystemInit() in system_stm32f10x.c
       return 0;
    }//end main
  31. Pawan says:

    Hi,I need to ask that…..i have to read the adc values continuously….suppose i have to blink an led on 4 converted values……i am enable to do this because i have to reset it everytime for the new conversion…..i want it continuously without resetting it…..m doing it an continuous mode bt still nt getting it…..Hw cn i get it…??

  32. Dennis says:

    greak work! Thanks a lot for the nice information about the STM32 ADC. Your really helped me ;-)

  33. Pingback: Usando ADC da placa SMT32Discovery, mode sem pegadinhas « Jedizone

  34. Ali says:

    Thank you very much for your Tutorial. It is really a good start for ST32 Controllers!

  35. may says:

    Can u tell how did u toggled an output pin?
    Can you share the code?

  36. Peter Harrison says:

    If you have a look here:

    or at Tony’s comment a little further up (Oct ’11), you should find enough to get you going.

  37. may says:

    Ok. thanks for help.

    I need to read ADc value for every 10micro sec.
    Is there any time or .. with which i can schedule it.

  38. may says:

    i would also like to know how did u give input to microcontroller..??

  39. Thank you for this great tutorial about the ADC’s for a STM32!
    I’m a beginner with the STM32 processors and now I’m a little bit better ;-)

    Thak you!

  40. D says:

    I’ve managed to get Tony’s code working (thank you very much!). Just wondering how you can configure this so the ADC value is passed out via a DAC?


  41. Gene Osborne says:

    I am having trouble compiling the example on 32stmvldiscovery.
    it is giving me an error when it reaches ODR = 00000000000000000 cannot find a reference to the term ODR.
    if I remove the lines referencing ODR it appears to work in the debugger.

    Thanks in advance for any support
    Gene Osborne

  42. Peter Harrison says:

    In case the author of the comment does not notice, he seems just to be turning on some LEDs and has defined ODR elsewhere. Just use whatever code is appropriate for your platform for light ing up indicators or whatever.

  43. anonymous says:

    I tried to use the code as presented to digitise (using ADC1) the x and z axis output of an accelerometer. For some reason I am getting the same value for both channels. Has anyone noticed this when using the ACD1 to sample two different analogue signals one after the other?

  44. Peter Harrison says:

    Can you post a code fragment showing how you are doing the conversions?

  45. anonymous says:

    I am using the stm32F4 discovery with freertos. The code is as follows the functions to initiate and use the ADC are as follows, they are essentially the same as presented above

    void ADC_IO_Init(GPIO_TypeDef* GPIOx, uint32_t GPIO_Pin) {
      GPIO_InitTypeDef GPIO_InitStructure;
      GPIO_InitStructure.GPIO_Pin = GPIO_Pin;
      GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AN;
      GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
      GPIO_Init(GPIOx, &GPIO_InitStructure);
    void ADC_Enable(ADC_TypeDef* ADCx) {
      ADC_InitTypeDef ADC_InitStructure;
      ADC_CommonInitTypeDef ADC_CommonInitStructure;
      ADC_CommonInitStructure.ADC_Mode = ADC_Mode_Independent;
      ADC_CommonInitStructure.ADC_Prescaler = ADC_Prescaler_Div4;
      ADC_CommonInitStructure.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
      ADC_CommonInitStructure.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
      ADC_InitStructure.ADC_Resolution = ADC_Resolution_12b;
      ADC_InitStructure.ADC_ScanConvMode = DISABLE;
      ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
      ADC_InitStructure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
      ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
      ADC_InitStructure.ADC_NbrOfConversion = 1;
      ADC_Init(ADCx, &ADC_InitStructure);
      /* Enable the specified ADC*/
      ADC_Cmd(ADCx, ENABLE);
    u16 readADC(u8 channel, ADC_TypeDef* ADCx, uint8_t SampleTime) {
      ADC_RegularChannelConfig(ADCx, channel, 1, SampleTime);
      // Start the conversion
      // Wait until conversion completion
      while (ADC_GetFlagStatus(ADCx, ADC_FLAG_EOC) == RESET);
      // Get the conversion value
      return (ADC_GetConversionValue(ADCx));
    void ADCPeripheralInit() {
      ADC_Enable(ADC1); //Enable ADC1 to do the conversion
      ADC_IO_Init(GPIOA, GPIO_Pin_1); //PA1 connects to Accelerometer Z-axis
      ADC_IO_Init(GPIOA, GPIO_Pin_3); //PA2 connects to Accelerometer X-axis
    The relevant tasks are listed below.
    int main(void) {
      RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //enable clock for GPIOA
      RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); //enable clock for ADC1
      ADCPeripheralInit(); //initialise the ADC peripheral
      xTaskCreate(tskSensor_Read, (signed char *) “Sensor”, configMINIMAL_STACK_SIZE,
        NULL, Sensor_Read_TASK_PRIORITY, &hSensor_Read);
      vTaskStartScheduler(); // This should never return.
      // Will only get here if there was insufficient memory to create
      // the idle task.
      for (;;);
    portTASK_FUNCTION(tskSensor_Read, pvParameters) {
      portTickType xLastWakeTime;
      SWV_puts(“starting task tskSensor_Read\n”);
      xLastWakeTime = xTaskGetTickCount();
      uint16_t x_raw = 0;
      uint16_t z_raw = 0;
      for (;;) {
        z_raw = readADC(3, ADC1, ADC_SampleTime_56Cycles);
        x_raw = readADC(1, ADC1, ADC_SampleTime_56Cycles);
        SWV_puts(“\nx_raw is “);
        SWV_puts(“\nz_raw is “);
        vTaskDelayUntil(&xLastWakeTime, (4000 / portTICK_RATE_MS));

    If you are curious SWV_puts() is very similar to printf, it uses the the debugger to print text to the pc screen. I would like to add that the code is behaving very strangely. For example if i call “z_raw = readADC(3, ADC1, ADC_SampleTime_56Cycles);” twice then everything seems to work ok. Alternatively putting a 1 second delay between reading the first and second pins seems to fix things as well. I have no idea what is happening here.

  46. Peter Harrison says:

    This looks pretty much as I would expect. The actual code I am using for a sample on a STM32F4 is this:

    #define MAX_TIMEOUT 1000000
    int16_t ADCReadChannel (ADC_TypeDef * ADCx, int channel)
      uint32_t timeOut;
      int16_t result;
      timeOut = 0;
      // select the ADC and the channel
      ADC_RegularChannelConfig (ADCx, channel, 1, ADC_SampleTime_15Cycles);
      // That may be an unacknowledged conversion so clear that
      ADC_ClearFlag (ADCx, ADC_FLAG_EOC);
      ADC_SoftwareStartConv (ADCx);
      while (ADC_GetFlagStatus (ADCx, ADC_FLAG_EOC) == RESET) {
        if (timeOut > MAX_TIMEOUT) {
      // Get the conversion value
      if (timeOut > MAX_TIMEOUT) {
        result = -1;
      } else {
        result = ADC_GetConversionValue (ADCx);
      return result;

    The only substantial difference being that I have cleared any pending conversion results before starting a new conversion.

    Otherwise, I would wonder about using the code inside a real time operating system. I have no idea what goes on inside FreeRTOS but that would be another place to look. What happens if you just perform the conversions outside of a task?

  47. anonymous says:

    OMG your the man!!!
    ADC_ClearFlag (ADCx, ADC_FLAG_EOC);
    seems to have done the trick.

    By the way would you mind if i link to this page from my blog?

  48. Peter Harrison says:

    By all means. Link away.

  49. Henry says:

    I dont like the STM Library-functions. To understand what is going on in the STM its better to read the reference and learn the registers and bits:

    This is an example how to initialize the ADC Word by Word as it is used to be with well known PICs, AVRs and so on:

    Unfortunately the stm32f10x.h is not fully implemented to controll all registers directly: to use DMA-Controll the DMA_TypeDef in the stm32f10x.h has to be repaired/added:

    typedef struct {
      __IO uint32_t ISR;
      __IO uint32_t IFCR;
      //self added, selbst hinzugefügt:
      __IO uint32_t CCR1;
      __IO uint32_t CMAR1;
      __IO uint32_t CPAR1;
      __IO uint32_t CNDTR1;
    } DMA_TypeDef;
    ADC1 Initialize - routine example by STM - Core - Adresses :
    __INLINE static void ADC1_Config_free(void) {
      //ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; 0B0000: Independent mode
      ADC1->CR1 = 0x00000000;
      //ADC_InitStructure.ADC_ScanConvMode = Disable;
      ADC1->CR1 |= (0 < < 8); // of course (0SQR3 = 0x00000008; //Kanal 8 als 1 Conversion
      ADC1->SMPR2 |= (5 < CR2 |= (0 < CR2 |= (1 < CR2 |= (1 < CR2)& (1 < 0);
      // Start ADC1 calibration
      ADC1->CR2 |= (1 < CR2)& (1 < 0);
      //ADC_SoftwareStartConvCmd(ADC1, ENABLE);
      ADC1->CR2 |= (1 < CR2 |= (1 << 22); //SW-Start
  50. Henry says:

    The comment editor is totaly crap . The code is deleted randomly between the characters. I give up.

  51. Peter Harrison says:

    It is.

    Generally, all you need to do is put tags around your code fragment as described in the note at the bottom of the editor window. Nonetheless, it is not much fun.

    However, I am interested in your contribution so, if you send me the listing that you want to see, I will edit it for you.

    I had a go at your original comment. Is that what you had in mind?

  52. Henry says:

    ok, last try:

    typedef struct {
    __IO uint32_t ISR;
    __IO uint32_t IFCR;
    //self added, selbst hinzugefügt:
    __IO uint32_t CCR1;
    __IO uint32_t CMAR1;
    __IO uint32_t CPAR1;
    __IO uint32_t CNDTR1;
    } DMA_TypeDef;

    //This is the ADC-init-function. i tried to translate the StdPeriph_Lib-functions to the STM32-register-calls:

    __INLINE static void ADC1_Config_free(void)
    //ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; 0B0000: Independent mode
    ADC1->CR1 = 0x00000000 ;
    //ADC_InitStructure.ADC_ScanConvMode = Disable;
    ADC1->CR1 |= (0<CR2 |= (0<CR2 |= (1<CR2 |= (7<CR2 |= (0<CR1 |= (0<SQR1 = 0x00100000;
    ADC1->SQR3 = 0x00000008; //Kanal 8 als 1 Conversion
    ADC1->SMPR2 |= (5<CR2 |= (0<CR2 |= (1<CR2 |= (1<CR2)& (1<0);

    // Start ADC1 calibration
    ADC1->CR2 |= (1<CR2)& (1<0);

    //ADC_SoftwareStartConvCmd(ADC1, ENABLE);
    ADC1->CR2 |= (1<CR2 |= (1<<22); //SW-Start

  53. Henry says:

    still doesnt work even with code tags.

  54. radamta says:

    thanks you every body. i very happy when i reapd this topic. it help me understand about adc for Stm32.

  55. Pingback: Работа с АЦП в STM32 | MyLinks

  56. SAMEER says:

    for the first program what are the header files …i m biginner so pls help me…and i am not able to compile my program in kiel arm7

  57. Entropy says:

    I don’t care if the original post was 2009 and we are 2014 but i have got to say Thank you mate. i am pleased with your hard working and explanation you provided throughout this post. just out of curiosity would we really need to change much stuffs to get your simple ADC program to work on a newer processors such as stm32f407vg other than specifying some extraneous data regarding things like GPIO, Mode, PuPd and OType? Thanks

  58. Peter Harrison says:

    If you have a look back to a comment in July 2012, you should find an example for the STM32F4 processors. The main change is that you need to perform a common ADC configuration and then one specific for the ADC converter you are using.

  59. user'ssssss says:

    I tried to use the code as presented to digitise (using ADC1) the x and z axis output of an accelerometer. For some reason I am getting the same value for both channels. Has anyone noticed this when using the ACD1 to sample two different analogue signals one after the other?

  60. Peter Harrison says:

    I do this all the time.

    Make sure your sampling times are correct and double check the hardware.

  61. dirk says:

    I am using the stm32f303x
    These functions do not appear in the STM Peripheral Library pdf:

  62. Peter Harrison says:

    I think they have been removed from the newer library version. You can leave them out as far as I know.

  63. Bui Pham Duc. says:

    Thanks for your useful post.

  64. Green Ye says:

    same as STM32F4 library, these functions are no longer exist. If you read the sample code for ADC you might figure out the difference between F103 and F3/F4

  65. Amna says:

    HI…I am using adc and then usart to send data to pc at timer interrupts.My code was working properly. checked it by sampling a signal from signal generator and displaying it on matlab GUI.But suddenly it stopped it correctly samples DC signal but any varying signal is sampled by just continuous 0′s and 1′s.I checked everything independently. Timer and usart are working accurately. The problem is with ADC. can anyone help me to resolve this?

  66. Amna says:

    My code is

    #include "stm32f4xx_adc.h"
    #include "stm32f4xx_gpio.h"
    #include "stm32f4xx_rcc.h"
    ADC_CommonInitTypeDef ADC_CommonInitStructure;
    volatile unsigned int ConvertedValue = 0; //Converted value readed from ADC
    volatile unsigned int sample_data;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    GPIO_InitTypeDef GPIO_InitStructure;
    //void INTTIM_Config(void);
    void INTTIM_Config (void)
      NVIC_InitTypeDef NVIC_InitStructure;
      /* Enable the TIM2 gloabal Interrupt */
      NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init (&NVIC_InitStructure);
      /* TIM2 clock enable */
      RCC_APB1PeriphClockCmd (RCC_APB1Periph_TIM2, ENABLE);
      /* Time base configuration */
      RCC->CFGR |= 0X1400;
      TIM_TimeBaseStructure.TIM_Period = 1000 - 1; // 1 MHz down to 1 KHz (1 ms)
      TIM_TimeBaseStructure.TIM_Prescaler = 42 - 1; // 24 MHz Clock down to 1 MHz (adjust per your clock)
      TIM_TimeBaseStructure.TIM_ClockDivision = 0;
      TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
      TIM_TimeBaseInit (TIM2, &TIM_TimeBaseStructure);
      /* TIM IT enable */
      TIM_ITConfig (TIM2, TIM_IT_Update, ENABLE);
      /* TIM2 enable counter */
      TIM_Cmd (TIM2, ENABLE);
    void init_USART1 (uint32_t baudrate)
      GPIO_InitTypeDef GPIO_InitStruct; // this is for the GPIO pins used as TX and RX
      USART_InitTypeDef USART_InitStruct; // this is for the USART1 initilization
      NVIC_InitTypeDef NVIC_InitStructure; // this is used to configure the NVIC (nested vector interrupt controller)
      RCC_APB2PeriphClockCmd (RCC_APB2Periph_USART1, ENABLE);
      RCC->CFGR |= 0xE000;
      /* enable the peripheral clock for the pins used by
      * USART1, PB6 for TX and PB7 for RX
      RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOB | RCC_AHB1Periph_GPIOA, ENABLE);
      GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // Pins 6 (TX) and 7 (RX) are used
      GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; // the pins are configured as alternate function so the USART peripheral has access to them
      GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	 // this defines the IO speed and has nothing to do with the baudrate!
      GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;	 // this defines the output type as push pull mode (as opposed to open drain)
      GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;	 // this activates the pullup resistors on the IO pins
      GPIO_Init (GPIOB, &GPIO_InitStruct);
      GPIO_InitStruct.GPIO_Pin = GPIO_Pin_0; // Pins 6 (TX) and 7 (RX) are used
      GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; // the pins are configured as alternate function so the USART peripheral has access to them
      GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	 // this defines the IO speed and has nothing to do with the baudrate!
      GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;	 // this defines the output type as push pull mode (as opposed to open drain)
      GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;	 // this activates the pullup resistors on the IO pins
      GPIO_Init (GPIOA, &GPIO_InitStruct);
      /* The RX and TX pins are now connected to their AF
      * so that the USART1 can take over control of the
      * pins
      GPIO_PinAFConfig (GPIOB, GPIO_PinSource6, GPIO_AF_USART1); //
      GPIO_PinAFConfig (GPIOB, GPIO_PinSource7, GPIO_AF_USART1);
      /* Now the USART_InitStruct is used to define the
      * properties of USART1
      USART_InitStruct.USART_BaudRate = baudrate;	 // the baudrate is set to the value we passed into this init function
      USART_InitStruct.USART_WordLength = USART_WordLength_8b;// we want the data frame size to be 8 bits (standard)
      USART_InitStruct.USART_StopBits = USART_StopBits_1;	 // we want 1 stop bit (standard)
      USART_InitStruct.USART_Parity = USART_Parity_No;	 // we don't want a parity bit (standard)
      USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // we don't want flow control (standard)
      USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // we want to enable the transmitter and the receiver
      USART_Init (USART1, &USART_InitStruct);	 // again all the properties are passed to the USART_Init function which takes care of all the bit setting
      /* Here the USART1 receive interrupt is enabled
      * and the interrupt controller is configured
      * to jump to the USART1_IRQHandler() function
      * if the USART1 receive interrupt occurs
      USART_ITConfig (USART1, USART_IT_RXNE, ENABLE); // enable the USART1 receive interrupt
      NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;	 // we want to configure the USART1 interrupts
      NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;// this sets the priority group of the USART1 interrupts
      NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	 // this sets the subpriority inside the group
      NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	 // the USART1 interrupts are globally enabled
      NVIC_Init (&NVIC_InitStructure);	 // the properties are passed to the NVIC_Init function which takes care of the low level stuff
    // finally this enables the complete USART1 peripheral
    void USART_puts (USART_TypeDef* USARTx, volatile int s)
    // wait until data register is empty
      while (! (USARTx->SR & 0x00000040));
      USART_SendData (USARTx, s);
    void TIM2_IRQHandler (void) //Timer Interupt for sending data
      if (TIM_GetITStatus (TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit (TIM2, TIM_IT_Update);
        GPIO_ToggleBits (GPIOA, GPIO_Pin_0);
        sample_data = (uint8_t) (ConvertedValue >> 4); //convert 12 bit data to 8 bits
        USART_puts (USART1, sample_data);
    void adc_configure()
      ADC_InitTypeDef ADC_init_structure; //Structure for adc confguration
      GPIO_InitTypeDef GPIO_initStructre; //Structure for analog input pin
    //Clock configuration
      RCC_APB2PeriphClockCmd (RCC_APB2Periph_ADC1, ENABLE); //The ADC1 is connected the APB2 peripheral bus thus we will use its clock source
      RCC_AHB1PeriphClockCmd (RCC_AHB1ENR_GPIOCEN, ENABLE); //Clock for the ADC port!! Do not forget about this one ;)
    //Analog pin configuration
      GPIO_initStructre.GPIO_Pin = GPIO_Pin_0;//The channel 10 is connected to PC0
      GPIO_initStructre.GPIO_Mode = GPIO_Mode_AN; //The PC0 pin is configured in analog mode
      GPIO_initStructre.GPIO_PuPd = GPIO_PuPd_NOPULL; //We don't need any pull up or pull down
      GPIO_Init (GPIOC, &GPIO_initStructre); //Affecting the port with the initialization structure configuration
    //ADC structure configuration
      ADC_init_structure.ADC_DataAlign = ADC_DataAlign_Right;//data converted will be shifted to right
      ADC_init_structure.ADC_Resolution = ADC_Resolution_12b;//Input voltage is converted into a 12bit number giving a maximum value of 4096
      ADC_init_structure.ADC_ContinuousConvMode = ENABLE; //the conversion is continuous, the input data is converted more than once
      ADC_init_structure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;// conversion is synchronous with TIM1 and CC1 (actually I'm not sure about this one :/)
      ADC_init_structure.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;//no trigger for conversion
      ADC_init_structure.ADC_NbrOfConversion = 1;//I think this one is clear :p
      ADC_init_structure.ADC_ScanConvMode = DISABLE;//The scan is configured in one channel
      ADC_Init (ADC1, &ADC_init_structure); //Initialize ADC with the previous configuration
    //Enable ADC conversion
      ADC_Cmd (ADC1, ENABLE);
    //Select the channel to be read from
      ADC_RegularChannelConfig (ADC1, ADC_Channel_10, 1, ADC_SampleTime_144Cycles);
    int adc_convert()
      ADC_SoftwareStartConv (ADC1); //Start the conversion
      while (!ADC_GetFlagStatus (ADC1, ADC_FLAG_EOC)); //Processing the conversion
      return ADC_GetConversionValue (ADC1); //Return the converted data
    int main (void)
      init_USART1 (19200);
      while (1) {
        ConvertedValue = adc_convert();//Read the ADC converted value
  67. sonoi says:

    i want to use two ADCs , but i don know that. Please help me!

  68. sonoi says:

    I am trying to use DMA for ADC1 and use alots of chanel in ADC1. please help me! please give me projec hihi

  69. Peter Harrison says:

    You just need to repeat the setup code for the other ADC and make appropriate references to it in the other function calls.

  70. Peter Harrison says:

    Which processor?

  71. sonoi says:

    i am learning STM32F103RC. Could Peter Harrison help me to study that ? thank you hi

  72. replay1 says:

    Good post. Right now I’m still learning about FreeRTOS and how to use it, so this example might be a bit too complex for me, but it’s still useful. Any idea how to start and what examples to use for learning FreeRTOS basics? I’m using STM32F0 Discovery board. Thanks!

  73. Peter Harrison says:

    I am afraid I know nothing about FreeRTOS.

  74. R1ddl3 says:

    Hi, i’m working on a STM32f10x and i’d like to know if there is a way to change the resolution of its ADC from 12 bits to 8 bits.

  75. Peter Harrison says:

    It is a 12 bit converter. If you only want 8 bits of resolution you will have to either leave it right aligned and divide by 16 or set it to left align the result and only use the upper 8 bits.

  76. R1ddl3 says:

    yes, but then i would be loosing some info, correct? I want to adjust its resolution to 8 bits instead of 12:P Do you know if there is any way of doing that? Thanks in advance.

  77. Peter Harrison says:

    It makes no difference. Eight bits is eight bits. You will have lower resolution than with ten or twelve bits.

  78. R1ddl3 says:

    I understand. What if i wanted to go for 16 bits reso on the same microcontroller? Would there be a way of doing this?

  79. Green Ye says:

    alright, easy way here:
    use your 12 bit output multiply by 65536 then divide it by 4096 if you want a 16 bits output without changing any setting above. You won’t get any extra info anyway since the max is 12 bits.

Leave a Reply