7
\$\begingroup\$

I'm trying to read internal temperature sensor. Each time, the value from ADC conversion is 296 which results in negative temperature. Should I add something to the code below, enable some peripheral or my calculations are wrong?

#define TEMP_SENSOR_AVG_SLOPE_MV_PER_CELSIUS                        2.5f
#define TEMP_SENSOR_VOLTAGE_MV_AT_25                                760.0f
#define ADC_REFERENCE_VOLTAGE_MV                                    1210.0f
#define ADC_MAX_OUTPUT_VALUE                                        4095.0f

int32_t sensorValue, temperature;

__HAL_ADC_ENABLE(&hadc1);

// Disable Vbat signal from input channel and wake up temp sensor from power down mode
ADC->CCR &= ~(ADC_CCR_TSVREFE);


HAL_ADC_Start(&hadc1);
if(HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
{
    sensorValue = (int32_t)HAL_ADC_GetValue(&hadc1);
    HAL_ADC_Stop(&hadc1);
    sensorValue = sensorValue * ADC_REFERENCE_VOLTAGE_MV / ADC_MAX_OUTPUT_VALUE;
    temperature = (int32_t)((sensorValue - TEMP_SENSOR_VOLTAGE_MV_AT_25) / TEMP_SENSOR_AVG_SLOPE_MV_PER_CELSIUS + 25);
}
else
{
    temperature = -273;
}

return temperature;

--EDIT

#define TEMP_SENSOR_AVG_SLOPE_MV_PER_CELSIUS                        2.5f
#define TEMP_SENSOR_VOLTAGE_MV_AT_25                                760.0f
#define ADC_REFERENCE_VOLTAGE_MV                                    3300.0f
#define ADC_MAX_OUTPUT_VALUE                                        4095.0f
#define TEMP110_CAL_VALUE                                           ((uint16_t*)((uint32_t)0x1FFF7A2E))
#define TEMP30_CAL_VALUE                                            ((uint16_t*)((uint32_t)0x1FFF7A2C))
#define TEMP110                                                     110.0f
#define TEMP30                                                      30.0f

int32_t temperature;
float sensorValue;
float adcCalValue30 = (float)(*TEMP30_CAL_VALUE);
float adcCalValue110 = (float)(*TEMP110_CAL_VALUE);

__HAL_ADC_ENABLE(&hadc1);

// Disable Vbat signal from input channel and wake up temp sensor from power down mode
ADC->CCR |= ADC_CCR_TSVREFE;
ADC->CCR &= ~ADC_CCR_VBATE ;


HAL_ADC_Start(&hadc1);
if(HAL_ADC_PollForConversion(&hadc1, 100) == HAL_OK)
{
    sensorValue = (float)HAL_ADC_GetValue(&hadc1);
    HAL_ADC_Stop(&hadc1);
    temperature = (int32_t)((TEMP110 - TEMP30) / ((float)(*TEMP110_CAL_VALUE) - (float)(*TEMP30_CAL_VALUE)) * (sensorValue - (float)(*TEMP30_CAL_VALUE)) + TEMP30);
}
else
{
    temperature = -273;
}

return temperature;

Temperature calculation formula Calibration values

ADC configuration:

  ADC_ChannelConfTypeDef sConfig;

    /**Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion) 
    */
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.ScanConvMode = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }

    /**Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time. 
    */
  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
  sConfig.Rank = 1;
  sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

I'm getting the output around 35 degrees. Is it okay to have such a big offset?

\$\endgroup\$
4
  • 2
    \$\begingroup\$ @VIPPER always mention the model \$\endgroup\$ Aug 16, 2017 at 21:54
  • \$\begingroup\$ It's STM32F429ZIT6 \$\endgroup\$
    – VIPPER
    Aug 17, 2017 at 5:22
  • \$\begingroup\$ You have an error in formula. Must be instead (watch the last bracket): temperature = (int32_t)((TEMP110 - TEMP30) / ((float)(*TEMP110_CAL_VALUE) - (float)(*TEMP30_CAL_VALUE)) * (sensorValue - (float)(*TEMP30_CAL_VALUE))) + TEMP30; \$\endgroup\$
    – kir
    Jan 23, 2018 at 3:18
  • \$\begingroup\$ Note that ST does provide a macro to compute the temperature in stm32xxxx_hal_adc.h named __HAL_ADC_CALC_TEMPERATURE \$\endgroup\$
    – Loufylouf
    Jun 16, 2022 at 12:13

3 Answers 3

7
\$\begingroup\$

Like PeterJ pointed out, the first flaw is that you have to set the ADC_CCR_TSVREFE bit and not reset it.

I've no idea how you set the sample and hold time, but I hope it is correct. It has to be at least 10 µs for an accurate measurement (datasheet section about temperature sensor).

Your next big flaw is in your thinking that the reference voltage is 1.21 V. That is the nominal value for the \$V_{refint}\$. This voltage is not the reference voltage for the ADC. The reference voltage is usually \$V_{DDA}\$ or a different externally supplied voltage on the \$V_{REF+}\$-pin. But it can't be higher than \$V_{DDA}\$ nor can it be lower than \$V_{DDA}\$-1.2 V.

With your conversion result of 950, and taking 3.3 V you would end up with 27.2 °C, which seems like a good start. If you have 3 V it'd be -0.6 °C, which also seems okay.

Why do I consider -0.6 °C a good value? Because calculation using the average values is crap.

I don't know why, but STM doesn't promote their calibrated values much. Every device has two ADC raw values taken at 30 °C and 110 °C at 3.3 V stored internally. Using those values, you end up with more reasonable temperature values without performing calibration.


Something along these lines should do:

// see datasheet for position of the calibration values, this is for STM32F429
const uint16_t* const ADC_TEMP_3V3_30C =  reinterpret_cast<uint16_t*>(0x1FFF7A2C);
const uint16_t* const ADC_TEMP_3V3_110C =  reinterpret_cast<uint16_t*>(0x1FFF7A2E);
const float CALIBRATION_REFERENCE_VOLTAGE = 3.3F;

const float REFERENCE_VOLTAGE = 3.0F; // supplied with Vref+ or VDDA

// scale constants to current reference voltage
float adcCalTemp30C = static_cast<float>(*ADC_TEMP_3V3_30C) * (REFERENCE_VOLTAGE/CALIBRATION_REFERENCE_VOLTAGE);
float adcCalTemp110C = static_cast<float>(*ADC_TEMP_3V3_110C) * (REFERENCE_VOLTAGE/CALIBRATION_REFERENCE_VOLTAGE);

uint16_t adcTempValue = SAMPLED VALUE;

float temperature = (static_cast<float>(adcTempValue) - adcCalTemp30C)/(adcCalTemp110C - adcCalTemp30C) * (110.0F - 30.0F) + 30.0F;

I'm used to C++, so maybe not your coding style, but that shouldn't be a big problem.

\$\endgroup\$
5
  • \$\begingroup\$ I've found these calibration values in the reference manual of STM32F0. This is really strange that in STM32F4 reference manual I couldn't find it. Moreover, they described the procedure of reading temperature sensor value with ADC and didn't mention about any calibration values. However, these values appear in the datasheet... from my point of view, it's a little mess in their docs. \$\endgroup\$
    – VIPPER
    Aug 17, 2017 at 18:06
  • \$\begingroup\$ Regarding STM32L1 AN3964 app note, the calibration reference voltage is 3V, not 3.3V as you mentioned above. Is that correct? \$\endgroup\$
    – VIPPER
    Aug 17, 2017 at 18:22
  • \$\begingroup\$ I have no idea how you came up with 27.2 degrees :D \$\endgroup\$
    – VIPPER
    Aug 17, 2017 at 18:47
  • \$\begingroup\$ @VIPPER not sure why you care for the STM32L1, but yeah in that device the calibration is done at 3 V. On the F429 it is done at 3.3 V. As for the 27.2 °C: I used your formulas with 950 as ADC value but changed the ADC reference voltage from 1210 mV to 3300 mV. \$\endgroup\$
    – Arsenal
    Aug 18, 2017 at 6:32
  • \$\begingroup\$ And for your edit: we got a deviation of up to 6 K with the internal sensor. If it is more, something seems wrong (measure Vdda to make sure it is 3.3 V for example) \$\endgroup\$
    – Arsenal
    Aug 18, 2017 at 6:37
4
\$\begingroup\$
// Disable Vbat signal from input channel and wake up temp sensor from power down mode
ADC->CCR &= ~(ADC_CCR_TSVREFE);

It actually right opposite :)

Reading the temperature To use the sensor: 3. Select ADC1_IN18 input channel. 4. Select a sampling time greater than the minimum sampling time specified in the datasheet. 5. Set the TSVREFE bit in the ADC_CCR register to wake up the temperature sensor from power down mode 6. Start the ADC conversion by setting the SWSTART bit (or by external trigger) 7. Read the resulting V SENSE data in the ADC data register 8. Calculate the temperature using the following formula: Temperature (in °C) = {(V SENSE – V 25 ) / Avg_Slope} + 25 Where: – V 25 = V SENSE value for 25° C – Avg_Slope = average slope of the temperature vs. V SENSE curve (given in mV/°C or µV/°C) Refer to the datasheet electrical characteristics section for the actual values of V 25 and Avg_Slope.

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Thanks, you are right. Now i can read higher value after conversion, around 950 which results in -166 degrees... but this is a progress :D \$\endgroup\$
    – VIPPER
    Aug 17, 2017 at 5:24
  • \$\begingroup\$ Why you do not properly initialise the ADC? After power up you can do this when you program the registers, but not using HAL \$\endgroup\$ Aug 17, 2017 at 7:35
1
\$\begingroup\$

I suggest comparing your results with the __HAL_ADC_CALC_TEMPERATURE macro that ST supplies (e.g. in stm32g4xx_hal_adc.h for STM32G4).

In your case I think the call would look something like this:

#define YOUR_ACTUAL_VREF_IN_MV 3300
temperature = __HAL_ADC_CALC_TEMPERATURE(YOUR_ACTUAL_VREF_IN_MV, sensorValue, ADC_RESOLUTION_12B);

Also, you did not mention running the built-in ADC calibration procedure on start-up, but failing to do so can also lead to inaccurate ADC results. See HAL_ADCEx_Calibration_Start, AN3964, and your device's datasheet / reference manual for more info.

(from stm32g4xx_hal_adc.h)

#define __HAL_ADC_CALC_TEMPERATURE(__VREFANALOG_VOLTAGE__,\
                                   __TEMPSENSOR_ADC_DATA__,\
                                   __ADC_RESOLUTION__) \
__LL_ADC_CALC_TEMPERATURE((__VREFANALOG_VOLTAGE__),\
                          (__TEMPSENSOR_ADC_DATA__),\
                          (__ADC_RESOLUTION__))

(from stm32g4xx_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 calibration values
  *         stored in system memory for each device during production.
  * @note   Calculation formula:
  *           Temperature = ((TS_ADC_DATA - TS_CAL1)
  *                           * (TS_CAL2_TEMP - TS_CAL1_TEMP))
  *                         / (TS_CAL2 - TS_CAL1) + TS_CAL1_TEMP
  *           with TS_ADC_DATA = temperature sensor raw data measured by ADC
  *                Avg_Slope = (TS_CAL2 - TS_CAL1)
  *                            / (TS_CAL2_TEMP - TS_CAL1_TEMP)
  *                TS_CAL1   = equivalent TS_ADC_DATA at temperature
  *                            TEMP_DEGC_CAL1 (calibrated in factory)
  *                TS_CAL2   = equivalent TS_ADC_DATA at temperature
  *                            TEMP_DEGC_CAL2 (calibrated in factory)
  *         Caution: Calculation relevancy under reserve that calibration
  *                  parameters are correct (address and data).
  *                  To calculate temperature using temperature sensor
  *                  datasheet typical values (generic values less, therefore
  *                  less accurate than calibrated values),
  *                  use helper macro @ref __LL_ADC_CALC_TEMPERATURE_TYP_PARAMS().
  * @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 either known from
  *         user board environment or can be calculated using ADC measurement
  *         and ADC helper macro @ref __LL_ADC_CALC_VREFANALOG_VOLTAGE().
  * @note   On this STM32 series, calibration data of temperature sensor
  *         corresponds to a resolution of 12 bits,
  *         this is the recommended ADC resolution to convert voltage of
  *         temperature sensor.
  *         Otherwise, this macro performs the processing to scale
  *         ADC conversion data to 12 bits.
  * @param  __VREFANALOG_VOLTAGE__  Analog reference 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
  *         @arg @ref LL_ADC_RESOLUTION_10B
  *         @arg @ref LL_ADC_RESOLUTION_8B
  *         @arg @ref LL_ADC_RESOLUTION_6B
  * @retval Temperature (unit: degree Celsius)
  */
#define __LL_ADC_CALC_TEMPERATURE(__VREFANALOG_VOLTAGE__,\
                                  __TEMPSENSOR_ADC_DATA__,\
                                  __ADC_RESOLUTION__)                            \
(((( ((int32_t)((__LL_ADC_CONVERT_DATA_RESOLUTION((__TEMPSENSOR_ADC_DATA__),     \
                                                  (__ADC_RESOLUTION__),          \
                                                  LL_ADC_RESOLUTION_12B)         \
                 * (__VREFANALOG_VOLTAGE__))                                     \
                / TEMPSENSOR_CAL_VREFANALOG)                                     \
      - (int32_t) *TEMPSENSOR_CAL1_ADDR)                                         \
   ) * (int32_t)(TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP)                    \
  ) / (int32_t)((int32_t)*TEMPSENSOR_CAL2_ADDR - (int32_t)*TEMPSENSOR_CAL1_ADDR) \
 ) + TEMPSENSOR_CAL1_TEMP                                                        \
)

In my case, I wanted to try to get a little finer granularity (0.01C, even if it's noisy) while still keeping the computational cost down, and this is what I came up with:

static int32_t TS_SUB;
static int32_t TS_MULT;
static int32_t TS_DIV;
static int32_t TS_ADD;


// Load device-specific constants on start-up (e.g. right after ADC cal)
  TS_SUB = *TEMPSENSOR_CAL1_ADDR;
  TS_MULT = 100*(TEMPSENSOR_CAL2_TEMP - TEMPSENSOR_CAL1_TEMP);
  TS_DIV = *TEMPSENSOR_CAL2_ADDR - *TEMPSENSOR_CAL1_ADDR;
  TS_ADD = 100*TEMPSENSOR_CAL1_TEMP;

// later when processing ADC results
  int32_t temp = (your ADC value, adjusted for reference differences)
  temp -= TS_SUB;
  temp *= TS_MULT;
  temp /= TS_DIV;
  temp += TS_ADD;
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.