STM32 HAL Tutorial: Read the Internal Temperature Sensor using DMA
Abstract
Learn how to use STM32 DMA (Direct Memory Access) to read ADC values, including the internal temperature sensor. One ADC channel will be used to update PWM output and the other will be used to calculate the STM32’s temperature. Step-by-step guide for high-speed, non-blocking control using HAL.
1. Introduction
In Episode 11, we used timer interrupts to read ADC and update PWM.
Now we’ll optimize performance using DMA and calculating the Temperature using the internal temperature sensor, which allows:
- Continuous ADC sampling without CPU intervention
- Automatic transfer of ADC values to memory
- Efficient PWM updates in real-time
- High-speed applications like motor control or signal processing
By the end of this episode, you’ll be able to:
- Configure ADC with DMA using CubeMX.
- Continuously sample analog inputs without blocking the CPU.
- Map ADC data to PWM output efficiently.
The RM0444 details how to perform the temperature reading:
The datasheet shows the address to read the stored values needed and other needed information:
2. Prerequisites
- STM32 board with ADC sensor (potentiometer, LM35, or LDR)
- PWM output pin (LED or servo)
- STM32CubeIDE installed
Knowledge of ADC, PWM, and timer interrupts from Episodes 9–11
3. Configuring ADC & TIM in CubeMX
Step 1 – Open Project
- Create a new project in STM32CubeMX.
Step 2 – Configure ADC
- Enable ADC1 on your sensor pin (e.g., PA0) and the Temperature Sensor
- Resolution: 12-bit
- Continuous conversion mode: Enabled
- Enable DMA Continuous Requests
Step 3 – Configure Timer for PWM
- Configure the CLOCK to 64MHz
- Enable TIM3 or another general-purpose timer
- Configure PWM mode and channel (TIM3 CH3)
- Set period suitable for LED brightness or servo control (100Hz in this case)
- Enable NVIC
Step 4 – Configure UART2
- Configure the UART2 as: 115200 / 8 / N / 1
- Use PA2 and PA3
Step 5 – Generate Code
Click Project → Generate Code
4. Code Implementation
Rely on the USE CODE to copy and paste the code below:
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */
/* USER CODE BEGIN PD */
#define TEMP30_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFF75A8))
#define TEMP130_CAL_ADDR ((uint16_t*) ((uint32_t) 0x1FFF75CA))
/* USER CODE END PD */
/* USER CODE BEGIN PV */
uint16_t u16AdcBuf[2];
uint8_t bPrintFlag = 0;
/* USER CODE END PV */
/* USER CODE BEGIN 0 */
int __io_putchar(int ch)
{
HAL_UART_Transmit(&huart2,(uint8_t *) &ch, 1, 100);
return ch;
}
int32_t CalculateTemp(uint16_t TempSense)
{
int32_t temp;
TempSense = (3300 * TempSense)/ 3000;
temp = __LL_ADC_CALC_TEMPERATURE(3000, TempSense,LL_ADC_RESOLUTION_12B);
return temp;
}
/* USER CODE END 0 */
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)u16AdcBuf, 2);
HAL_TIM_Base_Start_IT(&htim3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
if(bPrintFlag)
{
bPrintFlag = 0;
printf("Duty Cycle: %lu %%\r\n", htim3.Instance->CCR3/100);
printf("Temperature: %li degC \r\n", CalculateTemp(u16AdcBuf[1]));
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)u16AdcBuf, 2);
}
}
/* USER CODE END 3 */
/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
uint16_t pwmValue;
// Map ADC value to PWM duty cycle
pwmValue = (u16AdcBuf[0] * 9999) / 4095;
// Update PWM output
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_3, pwmValue);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint16_t count = 0;
if(count++ > 100)
{
bPrintFlag = 1;
count = 0;
HAL_ADC_Stop_DMA(&hadc1);
}
}
/* USER CODE END 4 */
5. Hands-On Lab Example
- Connect potentiometer to ADC pin
- Connect a LED to the PWM pin
- Rotate potentiometer → LED brightness changes smoothly
- CPU is free for other tasks
- Optional: Periodically send the current Duty Cycle and Temperature over the UART to the terminal
Full Source Code: hackerembedded/STM32_EP12
6. Compiling and Running
- Build Project → Click hammer icon.
- Flash Project → Connect STM32 and run (Ctrl + F11).
- Test PWM Control:
- Rotate potentiometer → LED brightness changes.
- Main loop remains free to handle other tasks (e.g., UART communication, SPI, I²C sensors)
7. Advantages of Interrupt-Driven Approach
- Non-blocking ADC reads → Main loop free for multitasking
- High-speed sampling → Suitable for fast sensors
- Smooth PWM control → No jitter or lag
- Scalable → Multiple channels and PWM outputs handled efficiently
8. Common Issues & Fixes
| Issue | Cause | Solution |
|---|---|---|
| DMA not updating ADC value | DMA not started or linked | Call HAL_ADC_Start_DMA() |
| PWM output not responding | Timer PWM not started | Call HAL_TIM_PWM_Start() |
| LED flickers | ADC mapping or timer period wrong | Adjust mapping formula and timer period |
| Multiple ADC channels wrong | Buffer size mismatch | Ensure DMA buffer matches number of channels |


