0

While using STM32L4's ADC to measure its internal temperature sensor data, I am facing an issue.

The equation provided in STM32L4's reference manual, seems to showing improper results.

The equation is:

*Temperature = ((110-30) * (TS_DATA - TS_CAL_1) / (TS_CAL_2 - TS_CAL_1)) + 30*

where, TS_DATA = 945 (raw ADC data for temperature), TS_CAL_1 = 1035 (calibration point read from predefined memory address), TS_CAL_2 = 1373 (calibration point read from predefined memory address)

This results in 'Temperature = 8.69' at room temperature (about 26 Celsius), which is clearly incorrect.

My Code for the same is:

#define TS30    ((uint16_t*)((uint32_t)0x1FFF75A8))
#define TS110   ((uint16_t*)((uint32_t)0x1FFF75CA))
uint32_t ADC_Value;
double temperature;
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
   ADC_Value = HAL_ADC_GetValue(&hadc1);
   temperature = (double)ADC_Value - (uint32_t)*TS30;
   temperature *= (double)((uint32_t)110 - (uint32_t)30);
   temperature /= (double)(int32_t)((uint32_t)*TS110 - (uint32_t)*TS30);
   temperature += 30;
}
int main(){           /**Main Loop**/
 MX_ADC1_Init();
   HAL_ADCEx_Calibration_Start(&hadc1, ADC_SINGLE_ENDED);
   HAL_ADC_Start_IT(&hadc1);

   while(1) {
     HAL_Delay(400);
     HAL_ADC_Start_IT(&hadc1);
   }
}
static void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig;
  hadc1.Instance = ADC1;
  hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc1.Init.Resolution = ADC_RESOLUTION_12B;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc1.Init.LowPowerAutoWait = DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.NbrOfConversion = 1;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.NbrOfDiscConversion = 1;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc1.Init.DMAContinuousRequests = DISABLE;
  hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc1.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc1) != HAL_OK){
    _Error_Handler(__FILE__, __LINE__);
  }

  /**Configure Regular Channel**/
  sConfig.Channel = ADC_CHANNEL_TEMPSENSOR;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK){
   _Error_Handler(__FILE__, __LINE__);
  }
}

Please review the code and help me.

Maunik Patel
  • 3
  • 1
  • 3
  • That's not the formula I find in ST's [RM0394 reference guide](https://www.st.com/content/ccc/resource/technical/document/reference_manual/group0/b0/ac/3e/8f/6d/21/47/af/DM00151940/files/DM00151940.pdf/jcr:content/translations/en.DM00151940.pdf) on page 446. Where does the (110-30) come from? Why is it hard-coded in your source code? – Marcus Müller Oct 29 '18 at 07:23
  • @MarcusMüller it is given like that in an old revision of the reference manual. It seems ST changed their mind on when to calibrate different devices. They used to do it at 110 and 30 °C and now there are some devices where it is done at 130 and 30 °C. You have to read that value from the datasheet. – Arsenal Oct 29 '18 at 08:56
  • To answer your question I need: The exact device you are using, and the voltage you are using. If it is anything other than the reference value used during calibration you have to scale the values accordingly. – Arsenal Oct 29 '18 at 08:58
  • @Arsenal I am using STM32L432KBUx and I have applied 3.3V as reference voltage. – Maunik Patel Oct 29 '18 at 10:15

2 Answers2

4

Please review the code and help me.

Will do; less focus on code style than I'd usually do; more focus on the tech side of things, since we're on a EE platform, not on programmers.stackexchange.com:

$$ {\frac{\text{TS_CAL2_TEMP} – \text{TS_CAL1_TEMP}}{\text{TS_CAL2} – \text{TS_CAL1}}}(\text{TS_DATA} – \text{TS_CAL1})+30\,^°\text{C} $$

Floating Point math

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc){
   ADC_Value = HAL_ADC_GetValue(&hadc1);
   temperature = (double)ADC_Value - (uint32_t)*TS30;
   temperature *= (double)((uint32_t)110 - (uint32_t)30);
   temperature /= (double)(int32_t)((uint32_t)*TS110 - (uint32_t)*TS30);
   temperature += 30;
}

Oh!

So, first of all: double is totally over the top here. Your processor has a floating point unit, but only for single precision floating point numbers, and you gain absolutely nothing if you calculate this in double precision. It's not like you get 16 significant digits anywhere in this formula.

Anyway, unless you know very well what you're doing, don't use the FPU in an interrupt routine! There's a simple reason to that: FPU status registers usually aren't saved / restored on switching contexts on most platforms (and AFAIK, this applies especially to ARM). So, you're mixing things up. Also, the FPU itself can raise exceptions (e.g. when doing an illegal mathematical operation), and you really don't want nested exceptions here.

Do this in fixed point. There's absolutely no reason to use floating point here.

Don't do the math in the ISR

Then, almost none of this calculation needs to be done in the interrupt routine. Precalculate the factor \$f\$ (i.e. the fraction from the reference manual, p.446) and the added value \$s\$:

\begin{align} T&= \underbrace{\frac{\text{TS_CAL2_TEMP} – \text{TS_CAL1_TEMP}}{\text{TS_CAL2} – \text{TS_CAL1}}}_f(\text{TS_DATA} – \text{TS_CAL1})+30\,^°\text{C}\\ &=f\cdot\text{TS_DATA}+\underbrace{(-f\cdot\text{TS_CAL1} + 30)}_s\\ &=f\cdot\text{TS_DATA}+s \end{align}

So that you only need to do one multiplication and one addition in the ISR.

Doing it in fixed point

Note that your \$f\$ might not be properly representable as integer; but always remember that the choice of measuring temperature in °C is arbitrary; you could just as well multiply \$f\$ and \$s\$ with 1000 and have a value in °mC, which you can convert to °C whenever you actually need to – without losing any accuracy, because your ADC nor your sensor nor the natural noise would nor the observed phenomenon even have about 10 bits of information.

Misc

Also, didn't you forget to register your ADC conversion complete callback?

Marcus Müller
  • 88,280
  • 5
  • 131
  • 237
  • I don't even know which datasheet you're referring to. STM32L4 is a whole family of MCUs, they have dozens of datasheets. Could you link to the datasheet, please? – Marcus Müller Oct 29 '18 at 10:20
  • OK. Found 1 mistake. In datasheet, table 7 on page 36 says 'calibration points' are 30 and 130 Celsius. Reference manual RM0393 (page 444) and RM0394 (page 423) saws that temperature equation have 'FIXED' values of 'TS_CAL1_TEMP' and 'TS_CAL2_TEMP'. The 'Floating Point math' section is simply awesome. You are really thinking at the 'core' level. I have to appreciate your mind blowing knowledge. Last thing, 'HAL_ADC_ConvCpltCallback' is called automatically when ADC EOC / EOS interrupt is generated. – Maunik Patel Oct 29 '18 at 10:25
  • 1
    datasheet: https://www.st.com/resource/en/datasheet/stm32l432kb.pdf reference Manual: https://www.st.com/content/ccc/resource/technical/document/reference_manual/group0/b0/ac/3e/8f/6d/21/47/af/DM00151940/files/DM00151940.pdf/jcr:content/translations/en.DM00151940.pdf – Maunik Patel Oct 29 '18 at 10:30
  • cool, thanks! Ah, now everything makes sense: You're actually reading the values from the right memory locations; sorry to have doubted you! – Marcus Müller Oct 29 '18 at 10:33
2

You need to apply a scaling because your reference voltage is different from the reference voltage when the parts where calibrated.

They use a 3 V reference during calibration, you use a 3.3 V reference.

So the absolute value of the temperature sensor will not change, so the ADC value you get will be smaller than the one they got during calibration.

So your ADC value has to be scaled by a factor of 3.3 V / 3.0 V or 1.1.

If you enter that into the formula (and use the correct calibration temperatures of 130 and 30 °C) you end up with 31 °C. Still a bit off of your 26 °C, but that could be because your electronic is actually warmer, and even with the calibrated values we measured up to 6 K deviation in our tests, so I'd consider that as okayish.

(similar answer with a small code snippet, but there it was 110 and 30 °C)

Arsenal
  • 17,464
  • 1
  • 32
  • 59
  • So, New equation should be ((TS_CAL2_TEMP - TS_CAL2_TEMP) / (TS_CAL2 - TS_CAL1)) * ((TS_DATA * VDD / 3000) - TS_CAL1) + 30, where VDD = 3000 * VREFINT_CAL / VREFINT_DATA (equation provided on RM0394 Rev4's page 448. Is that right ?) – Maunik Patel Oct 29 '18 at 11:32
  • a doubt: Datasheet Rev4 says calibration temperatures are 30 and 130 and I don't know how to read them. My CubeMX generated code have 2 #define variables for the same: TEMPSENSOR_CAL2_TEMP = 110 and TEMPSENSOR_CAL1_TEMP = 30. Do u have any idea about this confliction? – Maunik Patel Oct 29 '18 at 11:43
  • @MaunikPatel The values are not readable in the device, you have to check the datasheet for that (in your case it is 130 and 30 °C). You can of course use a measurement for the reference voltage, but usually it is good enough to apply a static factor if your reference voltage comes from a stable supply. Seems like the CubeMX suffers from the same bug as the reference manual (do you use the latest version?) – Arsenal Oct 29 '18 at 12:05
  • Actually you should be referencing back to the internal bandgap, by seeing what that reads against the supply, then calculating the supply from that, and finally the temperature sensor from the supply. – Chris Stratton Oct 29 '18 at 12:31
  • @ChrisStratton for the most precise result yes. Though the calibration temperatures are given with +- 5 K, so that is going to limit the accuracy anyways. – Arsenal Oct 29 '18 at 13:26
  • @Arsenal: Please excuse my delayed response. I was updating CubeMX. After updation, TEMPSENSOR_CAL2_TEMP is still 110. Don't know why. Further, do you know, if it is possible to set 'Rain' (external impedence) to 50Kohm, for 12-bit resolution or not. – Maunik Patel Oct 30 '18 at 12:07
  • @MaunikPatel seems like they don't know about the bug then, you might want to report that. You cannot set Rain via the controller. The input impedance is a property of your input circuit. If it is too high, you need something like a voltage follower to reduce the impedance. – Arsenal Oct 30 '18 at 12:27