Introducing to STM32 ADC programming. Part2

After we had a quick overview of STM32 ADC peripheral we can start digging deeper in to specifics. In order to understand simple things lets go with simplest case – single conversion mode. In this mode ADC does one conversion and then stops. After ADC conversion result is stored in to 16-bit ADC_DR data register (remember that conversion result is 12-bit), then End of Conversion (EOC) flag is set and interrupt is generated if EOCIE flag is set. Same situation is if injected channel is converted. The difference is that result is stored in to corresponding ADC_DRJx register, JEOC flag is set and interrupt generated if JEOCIE flag is set.

STM_internal_temperature_sensor

In our example we are going to measure the internal temperature sensor value and send it using USART. Temperature sensor is internally connected to ADC1_IN16 channel. Algorithm will start single conversion and wait for conversion complete flag EOC. Then we are going to read ADC value from ADC_DR register, which later will be used to calculate in temperature value in Celsius and sent via USART. So we should see value in terminal screen. As usually we are going to use Standard peripheral library and CMSIS functions. The code for single conversion is pretty short:

#include "stm32f10x.h"
#include "usart.h"
#include <stdio.h>
uint16_t AD_value;
const uint16_t V25 = 1750;// when V25=1.41V at ref 3.3V
const uint16_t Avg_Slope = 5; //when avg_slope=4.3mV/C at ref 3.3V
uint16_t TemperatureC;
int main(void)
{
//initialize USART1
Usart1Init();
//enable ADC1 clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
ADC_InitTypeDef ADC_InitStructure;
//ADC1 configuration
//select independent conversion mode (single)
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
//We will convert single channel only
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
//we will convert one time
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
//select no external triggering
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
//right 12-bit data alignment in ADC data register
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
//single channel conversion
ADC_InitStructure.ADC_NbrOfChannel = 1;
//load structure values to control and status registers
ADC_Init(ADC1, &ADC_InitStructure);
//wake up temperature sensor
ADC_TempSensorVrefintCmd(ENABLE);
//ADC1 channel16 configuration
//we select 41.5 cycles conversion for channel16
//and rank=1 which doesn't matter in single mode
ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_41Cycles5);
//Enable ADC1
ADC_Cmd(ADC1, ENABLE);
//Enable ADC1 reset calibration register
ADC_ResetCalibration(ADC1);
//Check the end of ADC1 reset calibration register
while(ADC_GetResetCalibrationStatus(ADC1));
//Start ADC1 calibration
ADC_StartCalibration(ADC1);
//Check the end of ADC1 calibration
while(ADC_GetCalibrationStatus(ADC1));
//Start ADC1 Software Conversion
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
//wait for conversion complete
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)){}
//read ADC value
AD_value=ADC_GetConversionValue(ADC1);
//clear EOC flag
ADC_ClearFlag(ADC1, ADC_FLAG_EOC);
printf("\r\n ADC value: %d \r\n", AD_value);
TemperatureC = (uint16_t)((V25-AD_value)/Avg_Slope+25);
printf("Temperature: %d%cC\r\n", TemperatureC, 176);
while (1)
  {
	//interrupts does the job
  }
}

Using standard peripheral functions everything becomes obviously simple. First we set enable ADC peripheral clock:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);

It is connected to APB2 bus. We aren’t using any prescallers here so it works at 24MHz here.

Next thing is to set up ADC mode using ADC_initstructure. Here we select ADC_Mode_Independent which in our case is single conversion. Then we disable scan mode and continuous scan as we want only single conversion and stop. We also disable any external triggering and select data to be right aligned. In order to access temperature sensor we need to bring it from power down by writing TSVREFE bit in ADC_CR2 register. This is done by using command:

ADC_TempSensorVrefintCmd(ENABLE);

After ADC is set up and sensor is waked we can configure channel 16. Here we need to set several parameters including channel number, index in group and channel sampling time:

ADC_RegularChannelConfig(ADC1, ADC_Channel_16, 1, ADC_SampleTime_41Cycles5);

Then we enable ADC:

ADC_Cmd(ADC1, ENABLE);

After microcontroller is powered on it is recommended to run ADC self calibration. This calculates error correction codes for capacitors and reduces over all error in result.

//Enable ADC1 reset calibration register

ADC_ResetCalibration(ADC1);

//Check the end of ADC1 reset calibration register

while(ADC_GetResetCalibrationStatus(ADC1));

//Start ADC1 calibration

ADC_StartCalibration(ADC1);

//Check the end of ADC1 calibration

while(ADC_GetCalibrationStatus(ADC1));

Now its time to start conversion. The function for this is:

ADC_SoftwareStartConvCmd(ADC1, ENABLE);

Next thing is to wait for conversion complete by checking for ADC_FLAG_EOC flag in status register:

while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)){}

Then we can take ADC value from data register:

AD_value=ADC_GetConversionValue(ADC1);

After reading temperature sensor we get some 12-bit digital value. In order to calculate temperature we need to use formula:

Temperature = (V25-AD_value)/Avg_slope+25

V25 and Avg_slope values can be found on microcontroller datasheet:

Here we get that V25 is typically 1.41V and Avg_Slope=4.3. When using 3.3V supply as reference we get approximately V25=1750 and Avg_Slope=5. Having these temperature value can be easily calculated:

TemperatureC = (uint16_t)((V25-AD_value)/Avg_Slope+25);

Don’t actually rely on readings as manufacturer states it can vary from chip to chip up to 45ºC. It can serve only to detect temperature variations inside chip. It is always better to use external sensor for accurate readings.

This is how data looks in terminal screen. First temperature is read normally and second with some heat applied to microcontroller:

This code is only to demonstrate simplest ADC usage. Practically it is Very inefficient because there are loop used for waiting ADC to be complete. There are better ways of doing this like using interrupts or DMA. Next time we will try different ADC mode used in efficient way.

Example source code is here: STM32DiscoveryADCSingle.zip

Read

Bookmark the permalink.

11 Comments

  1. you are writing
    “Here we get that V25 is typically 1.41V and Avg_Slope=4.3. When using 3.3V supply as reference we get approximately V25=1750″

    how did you get 1750? thx

  2. These are ADC values of voltages taken from datasheet:
    VCC and VREF = 3.3V; ADC_value = 4096
    V25 = 1.41V; ADC_Value=1750 (calculated = (1.41/3.3)*4096)
    Avg_Slope = 4.3mV ADC_value=5 (calculated = (0.0043/3.3)*4096)

  3. Please tell me how to calculate sample time that is given 41 cycle in the code.

  4. Excellent explanations, thanks a lot!

  5. How to find out on which channel was made last conversion. Could you make some example of basic multi channel conversion, where every channel has another function (meaning of different source) and where will be seems how to get these different values without DMA?

  6. To AAA, sample time is selected in ADC_SMPR1 and ADC_SMPR2 registers. You can find this in datasheet.

  7. MartinP, you should keep track of different channel conversions.

  8. Where is it stated that the temp sensor is connected to ADC1_IN16 ? In DS (CD00161566.pdf) on page 20 it is written that temp sensor is connected to ADC12_IN16. But there is no ADC12, right? I am confused now. Could you please explain it?
    And I tried your code it works but I am getting 433 as adc result and 288 as temperatur. Ohh, maybe because of the clock settings? I have 8MHz external xtal and CPU runs at 72 MHz. What shall I do?
    Thanks.

  9. Temperature sensor in STM32 is internally connected to ADC1_IN16 channel. Probably there is an error in document where you found ADC12_IN16.
    If you read 433 ADC value, your temperature should be ~45C.

  10. @MUBIN re ADC12_IN16 I believe Both ADC1 and ADC2 both share channel 16, the ref man labels it ADC12_XXX. Someone correct me if I’m wrong.

Add Comment Register



Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>