DEV Community

ai pics
ai pics

Posted on Edited on

STM32 Internal Temperature Sensor Reading (With DMA + Timer Trigger) — Complete Guide & Example Code

STM32 microcontrollers include an internal temperature sensor connected to a dedicated ADC channel. The sensor is intended primarily for monitoring the temperature of the silicon die rather than measuring ambient temperature. It is therefore best suited to thermal trend detection, system health monitoring, thermal throttling, and failsafe logic.

With an adequate ADC sampling time, compensation for supply-voltage changes, deterministic triggering, and basic calibration, the internal sensor can still provide stable and repeatable readings for many embedded applications.

This tutorial shows how to:

  • Enable the internal temperature sensor and VREFINT channels
  • Trigger ADC conversions at 50 Hz using TIM3 TRGO
  • Transfer two ADC channels through DMA in circular mode
  • Compensate for VDD variation using VREFINT
  • Convert the measured sensor voltage into degrees Celsius
  • Calibrate and stabilize the result for a real product

Important: Always check the datasheet and reference manual for your exact STM32 part number. Temperature-sensor constants, factory calibration data, channel names, ADC resolution, trigger routing, and initialization details vary between STM32 families and sometimes between device revisions.

Table of Contents

  1. What the Internal Temperature Sensor Measures
  2. Measurement Flow and Conversion Equation
  3. Project Architecture: A 50 Hz Sampling Pipeline
  4. STM32CubeMX Configuration
  5. HAL Example Code
  6. Calibration and Accuracy Tips
  7. PCB Implementation Considerations
  8. Troubleshooting FAQ
  9. Wrap-Up

1. What the Internal Temperature Sensor Measures

The STM32 temperature sensor produces a voltage, normally identified as VSENSE, that changes with die temperature. This voltage is routed internally to an ADC channel; no external sensor pin is required.

A second internal ADC channel, VREFINT, exposes the MCU's internal bandgap reference. By measuring VREFINT, the firmware can estimate the actual analog supply voltage and compensate the VSENSE calculation for VDD variation.

This distinction matters because the ADC result is a ratio between the input voltage and the ADC reference. If the firmware assumes a fixed 3.3 V supply while the real VDD changes, the calculated temperature will drift even if the die temperature remains constant.

The internal sensor is useful for:

  • Detecting rising or falling die temperature
  • Initiating thermal throttling or a controlled shutdown
  • Recording thermal behavior during system tests
  • Comparing operating modes or firmware workloads
  • Detecting abnormal self-heating

It is not the right choice when the application requires precise room, enclosure, battery, surface, or remote-object temperature measurement. Those tasks normally require a properly positioned external sensor.

2. Measurement Flow and Conversion Equation

The basic measurement sequence is:

  1. Enable the internal temperature-sensor and VREFINT paths.
  2. Configure a sampling time that meets the datasheet minimum.
  3. Trigger a two-channel ADC sequence at a fixed rate.
  4. Read VREFINT and VSENSE.
  5. Estimate the actual VDD from VREFINT.
  6. Convert the raw temperature-sensor code into VSENSE.
  7. Convert VSENSE into temperature using the correct device constants.

For STM32 devices that use the typical V25 and average-slope method, the equation has this form:

Temperature (°C) = ((V25 − VSENSE) / Avg_Slope) + 25

Where:

  • V25 is the sensor output voltage at 25 °C, approximately 1.43 V for many STM32F1 parts.
  • Avg_Slope is the average sensor slope, approximately 4.3 mV/°C for many STM32F1 parts.
  • VSENSE is calculated from the raw ADC code after VDD compensation.

A 12-bit ADC has 4,096 quantization levels. At a 3.3 V reference, one ideal LSB is approximately 0.806 mV. The ADC/DAC resolution calculator is useful for checking LSB size, quantization limits, and the effect of reference voltage before comparing expected code changes with measured data.

If the MCU provides factory-programmed temperature calibration values such as TS_CAL1 and TS_CAL2, use them instead of generic V25 and Avg_Slope values. Two-point factory calibration usually provides a better starting point because it accounts for device-to-device variation.

3. Project Architecture: A 50 Hz Sampling Pipeline

The example uses a simple deterministic pipeline:

  • TIM3 generates a TRGO update event every 20 ms, producing a 50 Hz trigger rate.
  • ADC1 uses the timer event as the external trigger for its regular conversion group.
  • Each trigger starts a two-channel sequence: VREFINT first, then TempSensor.
  • DMA transfers both results into a two-element buffer in circular mode.
  • The conversion-complete callback toggles a GPIO and marks the new sample as ready.
  • The main loop estimates VDD, calculates VSENSE, converts it to temperature, and sends the result through UART.

The example target is the STM32F103C8T6, a 72 MHz Arm Cortex-M3 MCU with 12-bit ADCs, timers, DMA, and common serial interfaces. The same architecture applies to many other STM32 devices, but the internal channel definitions, calibration addresses, and HAL configuration can differ.

Using a timer rather than repeated software starts gives the ADC a consistent sampling interval. DMA keeps the two channel readings together and reduces timing variation caused by firmware execution.

4. STM32CubeMX Configuration

MCU or Board

The following settings are based on an STM32F103C8 device, such as a Blue Pill development board. Adapt the clock tree, ADC channels, and trigger source to the exact MCU used in your project.

RCC and Clock Tree

  • Use HSE with the PLL to obtain a 72 MHz system clock when appropriate for the board.
  • Keep the ADC clock within the datasheet limit. For an STM32F103 running PCLK2 at 72 MHz, a prescaler of 6 gives a 12 MHz ADC clock, below the 14 MHz maximum specified for this family.

ADC1

  • Enable scan conversion mode.
  • Set the regular sequence length to two conversions.
  • Configure Rank 1 as VREFINT and Rank 2 as TempSensor.
  • Disable continuous conversion; conversions will be started by the external timer trigger.
  • Select TIM3 TRGO as the external trigger.
  • Choose a sampling time that meets or exceeds the temperature sensor's minimum acquisition time.

For example, with a 12 MHz ADC clock, 239.5 sampling cycles correspond to approximately 19.96 µs, which is longer than the commonly specified 17 µs minimum for STM32F1 internal channels.

CubeMX normally generates the required internal-channel enable logic, but verify that the temperature-sensor/VREFINT path is enabled on your device. On STM32F1, this is controlled through the TSVREFE bit.

DMA

  • Add the ADC1 DMA channel.
  • Select circular mode.
  • Use halfword peripheral and memory alignment.
  • Enable memory increment.
  • Set the buffer length to two halfwords.

TIM3

  • Select the internal clock source.
  • Configure the prescaler and auto-reload values for a 20 ms update interval.
  • Set TRGO to Update Event.

At a 72 MHz timer clock, one valid example is:

  • Prescaler: 23
  • Auto-reload: 59,999

The timer update frequency is then:

72 MHz / (23 + 1) / (59,999 + 1) = 50 Hz

USART1

  • Configure 115200 baud, 8 data bits, no parity, and 1 stop bit.

GPIO

  • Configure one output, such as PB0, as a timing probe.
  • Toggle it in the ADC conversion-complete callback.

5. HAL Example Code

This example uses V25 = 1.43 V, Avg_Slope = 4.3 mV/°C, and a typical VREFINT value of 1.20 V. These values are commonly associated with STM32F1 devices but must be checked against the datasheet for the exact part.

/*
 * Demo: STM32 internal temperature sensor
 * ADC + DMA + TIM3 TRGO at 50 Hz
 * Target style: STM32F103; adjust constants and configuration for your MCU
 */

#include "main.h"
#include <stdio.h>

/* Datasheet parameters: verify for the exact MCU */
#define AVG_SLOPE_mV_per_C   (4.3f)
#define V_AT_25C_V           (1.43f)
#define VREFINT_TYP_V        (1.20f)

ADC_HandleTypeDef   hadc1;
DMA_HandleTypeDef   hdma_adc1;
TIM_HandleTypeDef   htim3;
UART_HandleTypeDef  huart1;

/* Rank 1 = VREFINT, Rank 2 = VSENSE */
static volatile uint16_t adc_buf[2];
static volatile uint8_t new_sample = 0;

static float vdd_V = 0.0f;
static float vsense_V = 0.0f;
static float temperature_C = 0.0f;

static char line[48];

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_DMA_Init(void);
static void MX_ADC1_Init(void);
static void MX_TIM3_Init(void);
static void MX_USART1_UART_Init(void);

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();

  /* Calibrate the ADC before starting conversions. */
  HAL_ADCEx_Calibration_Start(&hadc1);

  /* Arm ADC + DMA first, then start the timer trigger. */
  HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, 2);
  HAL_TIM_Base_Start(&htim3);

  for (;;)
  {
    if (new_sample)
    {
      uint16_t vrefint_raw;
      uint16_t vsense_raw;

      /* Clear the flag, then take a local snapshot of the DMA buffer. */
      new_sample = 0;
      vrefint_raw = adc_buf[0];
      vsense_raw = adc_buf[1];

      if (vrefint_raw == 0U)
      {
        continue;
      }

      const float adc_fullscale = 4095.0f;

      /* Estimate VDD from the typical internal reference value. */
      vdd_V = (VREFINT_TYP_V * adc_fullscale) / (float)vrefint_raw;

      /* Convert the temperature-sensor ADC code into volts. */
      vsense_V = ((float)vsense_raw * vdd_V) / adc_fullscale;

      /* Convert VSENSE into degrees Celsius. */
      temperature_C =
          (((V_AT_25C_V - vsense_V) * 1000.0f) /
           AVG_SLOPE_mV_per_C) + 25.0f;

      int n = snprintf(line, sizeof(line), "%.2f\r\n", temperature_C);
      HAL_UART_Transmit(&huart1, (uint8_t *)line, (uint16_t)n, 50);
    }
  }
}

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
  if (hadc->Instance == ADC1)
  {
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    new_sample = 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

If the device provides a factory VREFINT calibration code measured at a known voltage, calculate VDD with the calibration value instead of the typical 1.20 V assumption:

VDD = (VREF_CAL_VOLTAGE × VREFINT_CAL_CODE) / ADC[VREFINT]

If TS_CAL1 and TS_CAL2 are available, scale the current ADC result to the calibration voltage and interpolate between the two temperature points. Follow the formula given in the reference manual or datasheet for that STM32 family.

6. Calibration and Accuracy Tips

Use the Correct Sampling Time

The internal temperature-sensor path has a relatively high source impedance and needs enough acquisition time to charge the ADC sample-and-hold capacitor. A sampling time below the datasheet minimum can create unstable or biased readings.

Compensate for VDD Variation

Read VREFINT in the same sequence as VSENSE. Using a fixed assumed supply voltage can turn ordinary regulator tolerance, USB voltage changes, or load transients into apparent temperature changes.

Prefer Factory Calibration

Factory-programmed VREFINT_CAL, TS_CAL1, and TS_CAL2 values are usually better than family-level typical constants because they account for some device-to-device variation.

Average Sensibly

Thermal behavior changes slowly. An 8- or 16-sample moving average is often enough to reduce visible jitter. Sampling at 10–50 Hz is normally adequate for thermal monitoring; very high sampling rates add processing and data without providing useful thermal bandwidth.

Calibrate in the Final Hardware

If absolute accuracy matters, compare the reading with a known-good external sensor placed close to the MCU package. Allow the system to reach thermal equilibrium, collect measurements at several operating points, and fit an offset or two-point correction.

Remember What Is Being Measured

The reading is die temperature. CPU activity, clock frequency, flash accesses, regulator losses, nearby power devices, copper area, airflow, and enclosure conditions all affect it. The value should not be expected to match room temperature.

7. PCB Implementation Considerations

Firmware compensation cannot correct every hardware problem. On a custom board, keep analog supply and reference decoupling close to the MCU, follow the manufacturer's grounding guidance, and prevent fast digital or switching-current return paths from disturbing the ADC reference network.

Board layout also changes the thermal result. A large copper area under and around the MCU can spread heat, while a nearby regulator, MOSFET, or high-current trace can warm the package. Test points for VDD, VDDA, the timing-probe GPIO, and relevant power rails make bring-up much easier.

When moving from a development board to a custom design, a small prototype PCB build gives you an opportunity to validate ADC noise, decoupling, trigger timing, thermal coupling, and sensor calibration before committing to production quantities.

8. Troubleshooting FAQ

Why is the temperature reading noisy or unstable?

  • Increase the ADC sampling time.
  • Average several readings.
  • Confirm that TIM3 TRGO is actually selected as the ADC trigger.
  • Check VDDA decoupling and the analog supply layout.
  • Verify that the DMA buffer order matches the configured ADC ranks.

Why does the reading drift when VDD changes?

The calculation is probably using a fixed reference voltage. Measure VREFINT during every sequence and use it to estimate the actual VDD.

Why do I get an obviously wrong result, such as −20 °C at room temperature?

  • Check whether VREFINT and TempSensor are reversed in the DMA buffer.
  • Verify that V25, Avg_Slope, and any calibration addresses match the exact MCU.
  • Confirm the ADC clock, resolution, and channel sampling time.
  • Make sure the internal sensor/VREFINT path is enabled.
  • Check whether the formula's slope direction matches the device documentation.

How do I verify the 50 Hz sampling rate?

Toggle a GPIO in HAL_ADC_ConvCpltCallback() and inspect it with an oscilloscope or logic analyzer. The pin changes state once per completed two-channel sequence, so transitions should occur every 20 ms. Because the pin alternates high and low, the complete square-wave period is 40 ms, corresponding to a displayed waveform frequency of 25 Hz.

Can I read the sensor without DMA?

Yes. Polling or interrupt-driven conversions can work, especially for a slow diagnostic measurement. DMA is useful because it keeps both channel results together, reduces CPU involvement, and supports a predictable timer-driven pipeline.

Why does snprintf() not print the floating-point value?

Some embedded C library configurations disable floating-point formatting to save flash. Enable float support in the linker settings or transmit a scaled integer value instead.

Should UART transmit at every 50 Hz sample?

It is acceptable for a short lab test at 115200 baud, but production firmware should avoid blocking communication in a time-sensitive loop. Consider decimating the log rate, buffering messages, or using DMA for UART transmission.

9. Wrap-Up

With timer-triggered ADC conversions, circular DMA, and VREFINT compensation, the STM32 internal temperature sensor becomes a practical monitor for thermal trends, system health, throttling, and safety logic.

For the most reliable result:

  • Use the datasheet sampling-time requirement.
  • Trigger measurements at a consistent rate.
  • Measure VREFINT alongside VSENSE.
  • Prefer factory calibration data where available.
  • Validate the result on the final PCB under realistic loads and airflow.

The internal sensor will not replace a precision ambient probe, but it can provide valuable information about what is happening inside the MCU and help firmware respond before thermal stress becomes a system failure.

Top comments (0)