AnalogIn + ADC_TEMP returns strange values

26 Sep 2019

Hi EveryOne!

I'm looking for a mistake in sensing of internal temperature of my Nucleo STM32F103RB.

I use the following code: AnalogIn aiIntTemp(ADC_TEMP); pc.printf("AnalogIn V %f\n\r", aiIntTemp.read()*VCC/1000);

ADC_TEMP is defined at PinNames.h: ADC internal channels ADC_TEMP = 0xF0, ADC_VREF = 0xF1,

The problem is that the value of ADC_TEMP is not changing in the required bounds. For example, at 60C it is 1.24V but it should be 1.5V at least.

And during heating of chip from 20 to 60 it changes only from 1.28 to 1.24. That is not true because I can check that using Arduino sketch at the same board. It returns correct values.

The ping at Arduino is defined as:

  1. ifdef ADC_CHANNEL_TEMPSENSOR
  2. define ATEMP (NUM_DIGITAL_PINS + 1)

So, it ATEMP=16 but ADC_TEMP=240.

Where I'm wrong?

PS. At the same time AnalogIn + read() provides me with the correct values of other analog inputs from pins, like A0. Tested with a voltmeter.

26 Sep 2019

Hello Vladislav,

Quote:

The problem is that the value of ADC_TEMP is not changing in the required bounds. For example, at 60C it is 1.24V but it should be 1.5V at least.

The STM32F103RB reference manual says:

  • The temperature sensor output voltage changes linearly with temperature. The offset of this line varies from chip to chip due to process variation (up to 45 °C from one chip to another). The internal temperature sensor is more suited to applications that detect temperature variations instead of absolute temperatures. If accurate temperature readings are needed, an external temperature sensor part should be used.
  • Obtain the temperature using the following formula:
    Temperature (in °C) = {(V25 - VSENSE) / Avg_Slope} + 25
    Where,
    V25 = VSENSE value for 25° C and
    Avg_Slope = Average Slope for curve between Temperature vs. VSENSE (given in mV/° C or μV/ °C).

According to the STM32F103RB datasheet:

SymbolParameterMinTypMaxUnit
Avg_SlopeAverage slope4.04.34.6mV/°C
V25Voltage at 25 °C1.341.431.52V

To satisfy such formula when the temperature is increased from 20° C to 60° C the voltage should drop rather than to increase to 1.5V.

Quote:

And during heating of chip from 20 to 60 it changes only from 1.28 to 1.24. That is not true because I can check that using Arduino sketch at the same board. It returns correct values.

  • Obviously 1.28V measured at 20° C is not consistent with the voltage indicated in the table above at 25° C. It should be greater than that.
  • What voltages did you obtain at 20° C and 60° C with the Arduino sketch ?
27 Sep 2019

Zoltan Hudak wrote:

Hello Vladislav,

  • Obviously 1.28V measured at 20° C is not consistent with the voltage indicated in the table above at 25° C. It should be greater than that.
  • What voltages did you obtain at 20° C and 60° C with the Arduino sketch ?

Hello Zoltan,

  • Yes, I fully agree with you that 1.28V is not enough and it doesn't change very well during the temperature changes.

I did some measures with Arduino and a pyrometer (BTW I have two Nucleo boards at different locations):

  • Surface 28C, Chip 37C, Chip 1,7V.
  • Surface 70C, Chip 66C, Chip 1,5V.
  • Surface 45C, Chip 55C, Chip 1,61V.

I have the following setup for both Arduino and Mbed environments:

#define CALX_TEMP 25
#define V25       1430
#define AVG_SLOPE 4300
#define VREFINT   1200

Plus I use LL_ADC_CALC_TEMPERATURE_TYP_PARAMS(AVG_SLOPE, V25, CALX_TEMP, VRef, ATEMP, LL_ADC_RESOLUTION) macro from HAL for getting temperature (but it is also possible to use your method).

The same board, measured via mbed 5.14:

  • Surface 27C, Chip 1,32V
  • Surface 62C, Chip 1,21V

I don't need this temp measurement to be precise. It is required only for shutdown the system in a case of overheating.

PS. So, from my point of view the voltage change at mbed is too small. I think that the reason can be a) wrong pin, b) wrong understanding of voltage at the internal thermometer. It can be not from 0V to 3.3V, it can be from 2V to 3.3V for example.

29 Sep 2019

Clearly, selecting Avg_Slope = 4.0 mV/°C and V25 = 1.34V (still within the datasheet) doesn't meet real data:

SENSE [V]Measured temp. [° C]Real temp.[° C]Delta [° C]
1.323027+3
1.2157.562-4.5

But for shutdown purposes you can select more 'safe' parameters, like Avg_Slope = 3.1 mV/°C and V25 = 1.33V:

SENSE [V]Measured temp. [° C]Real temp. [° C]Delta [° C]
1.3228.2327+1.23
1.2163.7162+1.7
29 Sep 2019

Do you mean I should play with the numbers to get more or less correct values?

But why at Arduino the board shows 0,2V change for 40 degrees but at MBED it is 0,11 almost twice less.

29 Sep 2019

Since you do not know the actual parameters of the given STM32F103 chip in advance, you have to calibrate it based on real test results. Below is the Arduino's class for STM32F103:

STM32ADC.h

class STM32ADC {
/...

private:

    adc_dev * _dev;
    voidFuncPtr _DMA_int;
    voidFuncPtr _ADC_int;
    voidFuncPtr _AWD_int;
    static constexpr float _AverageSlope = 4.3; // mV/oC   //4.0 to 4.6
    static constexpr float _V25 = 1.43; //Volts //1.34 - 1.52

};

STM32ADC.cpp

//...

/*
    This will read the Temperature and return something useful.
    Polling is being used.
*/
    float STM32ADC::readTemp(){
        unsigned int result = 0;
        float temperature = 0.0;
        result = adc_read(_dev, 16);
        temperature = (float)((_V25-result)/_AverageSlope)+ 25.0; 
        return temperature;
    }

//...

It seems that Arduino is using the same formula as Mbed.
What is your Arduino sketch to read the voltage and temperature?

29 Sep 2019

Arduino code is very simple:

void StatisticsUnit::InternalTempInit() {
  analogReadResolution(ADC_RESOLUTION);
}

int32_t StatisticsUnit::readVref()
{
  return (VREFINT * ADC_RANGE / analogRead(AVREF)); // ADC sample to mV
}
int32_t StatisticsUnit::readTempSensor(int32_t VRef)
{
  return (__LL_ADC_CALC_TEMPERATURE_TYP_PARAMS(AVG_SLOPE, V25, CALX_TEMP, VRef, analogRead(ATEMP), LL_ADC_RESOLUTION));

}

int32_t StatisticsUnit::readVoltage(int32_t VRef, uint32_t pin)
{
  return (__LL_ADC_CALC_DATA_TO_VOLTAGE(VRef, analogRead(pin), LL_ADC_RESOLUTION));
}
Serial.println("Input voltage, V:   "+(String)((float)readVref()/1000));
Serial.println("Controller temp, C: "+(String)readTempSensor(readVref()));
#define CALX_TEMP 25
#define V25       1430
#define AVG_SLOPE 4300
#define VREFINT   1200
#define ADC_RANGE 4096

and from arduino pins:

#define ATEMP        (NUM_DIGITAL_PINS + 1)
#define AVREF        (NUM_DIGITAL_PINS + 2)
30 Sep 2019

The __LL_ADC_CALC_DATA_TO_VOLTAGE macro is defined as:

stm32f1xx_ll_adc.h

/**
  * @brief  Helper macro to calculate the voltage (unit: mVolt)
  *         corresponding to a ADC conversion data (unit: digital value).
  * @note   Analog reference voltage (Vref+) must be known from
  *         user board environment or can be calculated using ADC measurement.
  * @param  __VREFANALOG_VOLTAGE__ Analog reference voltage (unit: mV)
  * @param  __ADC_DATA__ ADC conversion data (resolution 12 bits)
  *                       (unit: digital value).
  * @param  __ADC_RESOLUTION__ This parameter can be one of the following values:
  *         @arg @ref LL_ADC_RESOLUTION_12B
  * @retval ADC conversion data equivalent voltage value (unit: mVolt)
  */
#define __LL_ADC_CALC_DATA_TO_VOLTAGE(__VREFANALOG_VOLTAGE__,\
                                      __ADC_DATA__,\
                                      __ADC_RESOLUTION__)                      \
  ((__ADC_DATA__) * (__VREFANALOG_VOLTAGE__)                                   \
   / __LL_ADC_DIGITAL_SCALE(__ADC_RESOLUTION__)                                \
  )

As you can see , parameter #1, __VREFANALOG_VOLTAGE__, should be the analog voltage reference (Vref+) voltage (unit: mV). According to the User manual, in case of a 64-pin package (STM32F103RB) Vref+ is internally connected to the ADC voltage supply (VDDA), that is to VCC. I'd suggest to measure the actual VCC with digital voltmeter and use that value as Vref+ rather than assuming it's equal to 3300mV.

The __LL_ADC_CALC_TEMPERATURE_TYP_PARAMS macro is defined as:

stm32f1xx_ll_adc.h

/**
  * @brief  Helper macro to calculate the temperature (unit: degree Celsius)
  *         from ADC conversion data of internal temperature sensor.
  * @note   Computation is using temperature sensor typical values
  *         (refer to device datasheet).
  * @note   Calculation formula:
  *           Temperature = (TS_TYP_CALx_VOLT(uV) - TS_ADC_DATA * Conversion_uV)
  *                         / Avg_Slope + CALx_TEMP
  *           with TS_ADC_DATA      = temperature sensor raw data measured by ADC
  *                                   (unit: digital value)
  *                Avg_Slope        = temperature sensor slope
  *                                   (unit: uV/Degree Celsius)
  *                TS_TYP_CALx_VOLT = temperature sensor digital value at
  *                                   temperature CALx_TEMP (unit: mV)
  *         Caution: Calculation relevancy under reserve the temperature sensor
  *                  of the current device has characteristics in line with
  *                  datasheet typical values.
  *                  If temperature sensor calibration values are available on
  *                  on this device (presence of macro __LL_ADC_CALC_TEMPERATURE()),
  *                  temperature calculation will be more accurate using
  *                  helper macro @ref __LL_ADC_CALC_TEMPERATURE().
  * @note   As calculation input, the analog reference voltage (Vref+) must be
  *         defined as it impacts the ADC LSB equivalent voltage.
  * @note   Analog reference voltage (Vref+) must be known from
  *         user board environment or can be calculated using ADC measurement.
  * @note   ADC measurement data must correspond to a resolution of 12bits
  *         (full scale digital value 4095). If not the case, the data must be
  *         preliminarily rescaled to an equivalent resolution of 12 bits.
  * @param  __TEMPSENSOR_TYP_AVGSLOPE__   Device datasheet data: Temperature sensor slope typical value (unit: uV/DegCelsius).
  *                                       On STM32F1, refer to device datasheet parameter "Avg_Slope".
  * @param  __TEMPSENSOR_TYP_CALX_V__     Device datasheet data: Temperature sensor voltage typical value (at temperature and Vref+ defined in parameters below) (unit: mV).
  *                                       On STM32F1, refer to device datasheet parameter "V25".
  * @param  __TEMPSENSOR_CALX_TEMP__      Device datasheet data: Temperature at which temperature sensor voltage (see parameter above) is corresponding (unit: mV)
  * @param  __VREFANALOG_VOLTAGE__        Analog voltage reference (Vref+) voltage (unit: mV)
  * @param  __TEMPSENSOR_ADC_DATA__       ADC conversion data of internal temperature sensor (unit: digital value).
  * @param  __ADC_RESOLUTION__            ADC resolution at which internal temperature sensor voltage has been measured.
  *         This parameter can be one of the following values:
  *         @arg @ref LL_ADC_RESOLUTION_12B
  * @retval Temperature (unit: degree Celsius)
  */
#define __LL_ADC_CALC_TEMPERATURE_TYP_PARAMS(__TEMPSENSOR_TYP_AVGSLOPE__,\
                                             __TEMPSENSOR_TYP_CALX_V__,\
                                             __TEMPSENSOR_CALX_TEMP__,\
                                             __VREFANALOG_VOLTAGE__,\
                                             __TEMPSENSOR_ADC_DATA__,\
                                             __ADC_RESOLUTION__)               \
  ((( (                                                                        \
       (int32_t)(((__TEMPSENSOR_TYP_CALX_V__))                                 \
                 * 1000)                                                       \
       -                                                                       \
       (int32_t)((((__TEMPSENSOR_ADC_DATA__) * (__VREFANALOG_VOLTAGE__))       \
                  / __LL_ADC_DIGITAL_SCALE(__ADC_RESOLUTION__))                \
                 * 1000)                                                       \
      )                                                                        \
    ) / (__TEMPSENSOR_TYP_AVGSLOPE__)                                          \
   ) + (__TEMPSENSOR_CALX_TEMP__)                                              \
  )

As you can see , parameter #4, __VREFANALOG_VOLTAGE__, should be the analog voltage reference (Vref+) voltage (unit: mV). However, in your code above readVref() (calculating the internal reference voltage) is passed to the __VREFANALOG_VOLTAGE__ function rather than Vref+. VREFINT has nothing to do with measuring of the internal temperature. According to the User manual, in case of a 64-pin package (STM32F103RB) Vref+ is internally connected to the ADC voltage supply (VDDA), that is to VCC. So I'd suggest to try:

Serial.println("Controller temp, C: "+(String)readTempSensor(VCC_mV_measured));
// where "VCC_mV_measured" is VCC measured with external voltmeter (unit: mV).
03 Oct 2019

Hello Zoltan

I did:

1. Measured VCC it is 3296 mV so we can consider that it is exactly 3.3V because I'm not 100% sure in my multimeter precisness. 2. Used this value in trying to get temp of the chip. I didn't see any difference in the result.

I read once more datacheet for the chip and found (again) this reference:

2.3.23. The temperature sensor has to generate a voltage that varies linearly with temperature. The conversion range is between 2 V < VDDA < 3.6 V. The temperature sensor is internally connected to the ADC12_IN16 input channel which is used to convert the sensor output voltage into a digital value.

That makes me a little bit confused because we have hardcoded values that voltage should be 1.42 at 25C. But how can it be 1.42 if it starts from 2V... That is strange. Moreover I still confused that Arduino provides me correct (more or less true) results but mbed doesn't. And I don't think that the problem is in the calculations or VCC value. aiVREF.read()*VCC provides no significant change of voltage based on temp changes (but Arduino does at the same board).

03 Oct 2019

Hello Vladislav,

  • I think the sentence "The conversion range is between 2 V < VDDA < 3.6 V. " means that conversion works correctly when VDDA is within that range.
  • Vsense = 1.42V at 25° C is the typical value. It means that for the majority of STM32F103RB chips (typical chips) it's close to that value. But actually it could be any value between Min and Max (from 1.34V to 1.52V).
  • Your Arduino code is using macros which are available also in Mbed. Try to use the same code in your Mbed program to see if that makes any difference.
  • Could you please run the Internal_Temperature_F103RB program on your board and let me know the results? (Do not update the mbed library when importing the project into the online compiler! Build it as it is, without any modification.)
03 Oct 2019

Zoltan Hudak wrote:

Hello Vladislav,

  • I think the sentence "The conversion range is between 2 V < VDDA < 3.6 V. " means that conversion works correctly when VDDA is within that range.
  • Vsense = 1.42V at 25° C is the typical value. It means that for the majority of STM32F103RB chips (typical chips) it's close to that value. But actually it could be any value between Min and Max (from 1.34V to 1.52V).
  • Your Arduino code is using macros which are available also in Mbed. Try to use the same code in your Mbed program to see if that makes any difference.
  • Could you please run the Internal_Temperature_F103RB program on your board and let me know the results? (Do not update the mbed library when importing the project into the online compiler! Build it as it is, without any modification.)

  • So, you suggest to make an adjustment in terms of "the zero point" using current temperature and received voltage?
  • As I can judge I use the same macros in Arduino and mbed. Initially it was implemented with Arduino, then adapted to mbed. And keep in mind that I have two similar boards and the behaviour is the same with the code. Arduino works well, mbed doesn't.
  • I tried your code with MBED Studio, it hanged at this code while(HAL_ADC_PollForConversion(&hadc1, 1000000) != HAL_OK); (it waits for 1 mln of trials and then returns timeout). Anyway I compiled your code via Online compiler without upgrading of os and it works well:

Chip int voltage, V:    1.19
Chip temperature, C:    26.40

A0 V 1.788452
AnalogIn V      1.367498
AnalogIn V map  -1.613754
--------------------------------
temp = 36.8�C
temp = 38.7�C
temp = 39.2�C
temp = 39.4�C
temp = 39.6�C
temp = 40.0�C
temp = 40.0�C
temp = 40.0�C
temp = 40.0�C

The values are similar to Arduino's (values at the top are from mbed).

04 Oct 2019

If Internal_Temperature_F103RB works well with Mbed OS-2 than try to compile the following program with Mbed OS-5.14:

#include "mbed.h"

/*
 * STM32F103x data-sheet:
 * 5.3.19 Temperature sensor characteristics
 * Table 50. TS characteristics, Page 80
 */
const float AVG_SLOPE = 4.3E-03;    // typical slope (gradient) of temperature function  [V/°C]
const float V25 = 1.43;             // typical sensor's output voltage at 25°C [V]
//
DigitalOut  led1(LED1);
AnalogIn    sensor(ADC_TEMP);       // Analog input for internal temperature sensor
float       vSense;                 // sensor's output voltage [V]
float       temp;                   // sensor's temperature [°C]

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int main()
{
    while (true) {
        vSense = sensor.read() * 3.3;               // get sensor's  output voltage [V]
        printf("vSense = %3.2fV\r\n", vSense);      // print sensor's output voltage [V]
        /*
         * STM32F103xx Reference Manual:
         * 11.10 Temperature sensor
         * Reading the temperature, Page 235
         * Temperature (in °C) = {(V25 - Vsense) / Avg_Slope} + 25
         */
        temp = (V25 - vSense) / AVG_SLOPE + 25.0f;  // convert sensor's output voltage to temperature [°C]
        printf("temp = %3.1f%cC\r\n", temp, 176);   // display chip's temperature [°C]
        led1 = !led1;
        ThisThread::sleep_for(1000);
    }
}
04 Oct 2019

Zoltan Hudak wrote:

If Internal_Temperature_F103RB works well with Mbed OS-2 than try to compile the following program with Mbed OS-5.14:

Surprisingly it gives:

temp = 36.5�C
vSense = 1.38V
temp = 37.7�C
vSense = 1.37V
temp = 37.8�C
vSense = 1.38V
temp = 37.7�C
vSense = 1.38V
temp = 37.5�C
vSense = 1.38V
temp = 37.5�C
vSense = 1.38V
temp = 37.5�C
vSense = 1.38V

That looks more true than my code... And it responces on heating in the correct way:

vSense = 1.31V
temp = 54.0�C
vSense = 1.31V
temp = 53.8�C
04 Oct 2019

Hi Zoltan,

I did some investigation in order to understand what was wrong. And it seems that some mistake in the initial values was made. Exactly in these values:

#define V25       1.430
#define AVG_SLOPE 0.0043

So there was a difference in the dimention of V25 according to AVG_SLOPE. So the results were not very good. Thank you for your explanations.

PS. The next task is to understand what is wrong with ADC_VREF. It returns under mbed 1.2 and at Arduino 3.3 :-D

06 Oct 2019

Zoltan Hudak wrote:

The __LL_ADC_CALC_DATA_TO_VOLTAGE macro is defined as:

stm32f1xx_ll_adc.h

/**
  * @brief  Helper macro to calculate the voltage (unit: mVolt)
  *         corresponding to a ADC conversion data (unit: digital value).
  * @note   Analog reference voltage (Vref+) must be known from
  *         user board environment or can be calculated using ADC measurement.
  * @param  __VREFANALOG_VOLTAGE__ Analog reference voltage (unit: mV)
  * @param  __ADC_DATA__ ADC conversion data (resolution 12 bits)
  *                       (unit: digital value).
  * @param  __ADC_RESOLUTION__ This parameter can be one of the following values:
  *         @arg @ref LL_ADC_RESOLUTION_12B
  * @retval ADC conversion data equivalent voltage value (unit: mVolt)
  */
#define __LL_ADC_CALC_DATA_TO_VOLTAGE(__VREFANALOG_VOLTAGE__,\
                                      __ADC_DATA__,\
                                      __ADC_RESOLUTION__)                      \
  ((__ADC_DATA__) * (__VREFANALOG_VOLTAGE__)                                   \
   / __LL_ADC_DIGITAL_SCALE(__ADC_RESOLUTION__)                                \
  )

As you can see , parameter #1, __VREFANALOG_VOLTAGE__, should be the analog voltage reference (Vref+) voltage (unit: mV). According to the User manual, in case of a 64-pin package (STM32F103RB) Vref+ is internally connected to the ADC voltage supply (VDDA), that is to VCC. I'd suggest to measure the actual VCC with digital voltmeter and use that value as Vref+ rather than assuming it's equal to 3300mV.

Hello Zoltan, If everything is OK with the internal temp then something is wrong with VREF.

According to your (and datasheet) information VREF+ should be connected to VCC at F103RB. That is good. And manual measurement with a multimeter provides me result with 3.296V result. That is also good.

But if I try to measure this VREF with help of internal measurements then I receive not satisfied results: 1.2 V (that is internal .

At Arduino I receive 3,3V as expected.

For getting 1.2V value I use ordinary conversion: aiVREF.read() * VCC; where VCC=3.3 where aiVREF.read() returns just 0,36 that seems to be 1.2V but I expect 3.3V

According to this marco. I don't clearly understand in what format should I supply it with _ADC_Data_. SHould be it just 0.36 that I get from read() or it should be 23749 that I receive from read_u16.

#define __LL_ADC_CALC_DATA_TO_VOLTAGE(__VREFANALOG_VOLTAGE__,\
                                      __ADC_DATA__,\
                                      __ADC_RESOLUTION__)                      \
  ((__ADC_DATA__) * (__VREFANALOG_VOLTAGE__)                                   \
   / __LL_ADC_DIGITAL_SCALE(__ADC_RESOLUTION__)                                \
  )

At Arduino version the following macro is available (I'm not sure that it is used by Arduino sketch):

#define __LL_ADC_CALC_VREFANALOG_VOLTAGE(__VREFINT_ADC_DATA__,\
                                         __ADC_RESOLUTION__)                   \
  (((uint32_t)(*VREFINT_CAL_ADDR) * VREFINT_CAL_VREF)                          \
    / __LL_ADC_CONVERT_DATA_RESOLUTION((__VREFINT_ADC_DATA__),                 \
                                       (__ADC_RESOLUTION__),                   \
                                       LL_ADC_RESOLUTION_12B)                  \
  )

But it is missed exactly for my board (F103rb) when it is available for other F's.

Could you please help me with understanding what is wrong with ADC according to VREF?

16 Oct 2019

Hello Vladislav,

There are two reference voltages involved with similar names, which could be a bit confusing:

  • Internal reference voltage denoted as VREFINT. It's connected to ADC1 and (typically) is equal to 1.2V. aiVREF.read() returns the value of this internal reference voltage.
  • Input voltage reference denoted as VREF+ (shared with DAC). It is available for improving the resolution of ADC. On 100-pin and 144-pin packages, to ensure a better accuracy on low-voltage inputs and outputs, the user can connect a separate external reference voltage on VREF+. VREF+ is the highest voltage, represented by the full scale value, for an analog input (ADC) or output (DAC) signal. The voltage on VREF+ can range from 2.4 V to VDDA. STM32F103RB has a 64-pin package, so no such pin is available and VREF+ is internally (inside the chip) connected to the ADC voltage supply (VDDA).
17 Oct 2019

Thank you very much for explanation!