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:
<code>void ADC_Configuration(void)
{
  ADC_InitTypeDef  ADC_InitStructure;
  /* PCLK2 is the APB2 clock */
  /* ADCCLK = PCLK2/6 = 72/6 = 12MHz*/
  RCC_ADCCLKConfig(RCC_PCLK2_Div6);

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

  /* ADC1 Configuration ------------------------------------------------------*/
  /* ADC1 and ADC2 operate independantly */
  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, &amp;ADC_InitStructure);
  /* Enable ADC1 */
  ADC_Cmd(ADC1, ENABLE);

  /* Enable ADC1 reset calibaration register */
  ADC_ResetCalibration(ADC1);
  /* Check the end of ADC1 reset calibration register */
  while(ADC_GetResetCalibrationStatus(ADC1));
  /* Start ADC1 calibaration */
  ADC_StartCalibration(ADC1);
  /* Check the end of ADC1 calibration */
  while(ADC_GetCalibrationStatus(ADC1));
}
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.

Comments

Thanks for all the info you

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!

Thanks,

Simone

Since the end of conversion

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

The flag is reset when the

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.

Thanks for peteh‘s help,I

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。

Thank you very much for

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.

I want to make PID controller

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

I cannot help with this at

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

http://www.st.com/mcu/forums.html

I would like to know how did

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

I am afraid it was not

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.

EOC interrupt

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

EOC Interrupt

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

Post new comment

The content of this field is kept private and will not be shown publicly.