4
\$\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\$
  • \$\begingroup\$ What are the definitions of ADC_REFERENCE_VOLTAGE_MV and the other constants? \$\endgroup\$ – bitsmack Aug 16 '17 at 20:40
  • \$\begingroup\$ ADC_REFERENCE_VOLTAGE_MV is a bandgap voltage = 1210mV; ADC_MAX_OUTPUT_VALUE = 4095; TEMP_SENSOR_VOLTAGE_MV_AT_25 = 760mV; TEMP_SENSOR_AVG_SLOPE_MV_PER_CELSIUS = 2.5mV/C; \$\endgroup\$ – VIPPER Aug 16 '17 at 20:48
  • \$\begingroup\$ Thank you, but can you post the actual #define statements? It may be a signed/unsigned error or something similar. You can edit them into your question. \$\endgroup\$ – bitsmack Aug 16 '17 at 20:54
  • \$\begingroup\$ This is good code. However, asking how one should improve your code or something that should be added is not for us to say because this is your project. But you can ask us if there's something else that you want to add but you're not sure how script such thing. Figuring out how to take measurements (like temperature) is something you need to figure out on your own unless you're attempted something but there's something wrong and you can't figure out what's wrong. Don't forget to check the error log if something happens. \$\endgroup\$ – KingDuken Aug 16 '17 at 21:27
  • 1
    \$\begingroup\$ @VIPPER always mention the model \$\endgroup\$ – P__J__ Aug 16 '17 at 21:54
5
\$\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\$
  • \$\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 '17 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 '17 at 18:22
  • \$\begingroup\$ I have no idea how you came up with 27.2 degrees :D \$\endgroup\$ – VIPPER Aug 17 '17 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 '17 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 '17 at 6:37
3
\$\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\$
  • 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 '17 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\$ – P__J__ Aug 17 '17 at 7:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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