Hello! Welcome back to our module on data acquisition.
In the previous lesson, you successfully configured the ADC to perform a single-channel, software-triggered conversion. You read the analog voltage from a potentiometer and saw the raw digital result—a number between 0 and 4095—change in real-time using the debugger. This was a crucial first step in bridging the gap between the analog world and your microcontroller.
This lesson builds directly on that foundation. A raw number like 2486 is meaningless without context. Our goal today is to convert raw ADC digital values into corresponding physical units, like volts or degrees Celsius. This is the process that turns raw data into actionable information, a core task for any firmware engineer. We will explore the fundamental formulas for this conversion and apply them in a practical lab to read the MCU's internal temperature.
1. The Fundamental Conversion: From Digital Value to Voltage
The raw digital value from a 12-bit ADC (0-4095) represents a fraction of the reference voltage, Vref. The conversion is a straightforward ratiometric calculation.
The relationship is defined by this essential formula:
Here, Vref is the voltage that corresponds to the maximum ADC reading of 4095. On your STM32F103 Blue Pill board, the ADC's reference voltage is typically connected to the analog power supply (VDDA), which in turn is connected to your main 3.3V supply.

To understand this concept in more detail, let's turn to a clear written explanation.
STM32 ADC Reference Voltage Explained | ADC Tutorial Part 10
The article 'STM32 ADC Reference Voltage Explained' provides an excellent breakdown of what Vref is and presents the core formula for converting a raw ADC value back to voltage. Understanding this is non-negotiable for working with ADCs.
Please read the sections 'What is ADC Reference Voltage in STM32?' and 'How ADC Value Depends on VREF'. Focus on the main formula and the example provided. This will solidify your understanding of the relationship between the ADC value, Vref, and the calculated voltage.
Activity: Calculating Voltage from the Potentiometer
Let's apply this formula to the project from our last lesson.
-
Open the potentiometer project where you were reading a raw ADC value.
-
In
main.c, declare a new floating-point variable for the calculated voltage:/* USER CODE BEGIN 2 */ uint16_t adc_value; float voltage; /* USER CODE END 2 */ -
Inside the
while(1)loop, after you get theadc_value, add the conversion formula. We'll assume an idealVrefof 3.3V for now./* USER CODE BEGIN WHILE */ while (1) { HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); adc_value = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); // Convert raw value to voltage voltage = ((float)adc_value / 4095.0f) * 3.3f; HAL_Delay(100); /* USER CODE END WHILE */Note the use of
(float)andfto ensure the calculation is done using floating-point arithmetic for precision. -
Start a debug session. Add both
adc_valueandvoltageto the "Live Expressions" window. As you turn the potentiometer, you should now see the raw value change from ~0 to ~4095, and thevoltagevariable change correspondingly from ~0.0V to ~3.3V.
2. The Professional Approach: Achieving Accurate Readings
Assuming Vref is exactly 3.3V is fine for a simple demo, but in a professional setting, especially for automotive or battery-powered devices, this assumption is flawed. The supply voltage (VDD) can fluctuate due to load changes or battery discharge. Since Vref is tied to VDD, your reference changes, and your calculations become inaccurate.
The Solution: STM32 microcontrollers include a precise, internal, factory-calibrated voltage reference called VREFINT. It provides a stable voltage (typically around 1.2V) that does not change with VDD.
By measuring our own VREFINT with the ADC, we can work backward to calculate the actual current Vref (VDDA). This allows us to create highly accurate and robust measurement systems.
The video below explains this concept and shows how to implement it on your STM32F103 board.
STM32 ADC (Part 10): How to measure VREFINT and use Custom Reference Voltage
The video 'STM32 ADC (Part 10): How to measure VREFINT' explains why a stable reference is critical and demonstrates how to use the internal VREFINT to measure the actual supply voltage. This is a key technique for professional-grade firmware.
Please watch from 01:00 to 02:34 to understand the different voltage types (VDD, VDDA, Vref, VREFINT). Then, jump to the section specifically for your board, from 12:32 to 15:49. Pay close attention to how a 'calibration' value for VREFINT is first measured with a known supply, and then used to calculate the actual Vref later.
This technique is the foundation for our next lab, where we'll read the internal temperature sensor. The temperature formula requires an accurate voltage reading, so we'll use a VREFINT-based calculation to provide it.
3. Case Study: Reading the Internal Temperature Sensor
Now let's move beyond a simple potentiometer to a real sensor. Your STM32 has a built-in temperature sensor connected to an internal ADC channel. Converting its reading to degrees Celsius is more complex than a simple voltage conversion; it requires a specific formula and parameters found in the microcontroller's datasheet.
The general formula for the STM32F1 series is:
Where:
- is the actual voltage measured from the temperature sensor ADC channel.
- is the sensor's voltage output at 25°C.
Avg_Slopeis the sensor's average change in voltage per degree Celsius (in mV/°C).
The values for and Avg_Slope are device-specific and must be found in the datasheet.

The article below provides a fantastic summary and the complete code structure for implementing this.
STM32 Internal Temperature Sensor Reading + Example Code
The article 'STM32 Internal Temperature Sensor Reading' clearly lays out the steps and the C code for calculating temperature. It combines the VREFINT measurement for accuracy with the temperature sensor formula.
Read sections '2. STM32 Internal Temperature Sensor Reading' and 'STM32 Internal Temperature Sensor Calibration'. Then, review the C code example. Notice how it first calculates the actual V_Ref using the VREFINT reading, then calculates V_Sense, and finally plugs that into the temperature formula. This multi-step process is what ensures an accurate result.
4. Hands-On Lab: Implementing the Temperature Sensor
Let's build a project that accurately measures the MCU's core temperature.
Hardware Setup
No external components are needed! We are using only the internal peripherals of the STM32.
STM32CubeIDE Configuration
- Create a new project or start from a clean configuration.
- Enable the
Serial Wiredebug interface. - In the
Analog->ADC1section, enable two channels:- Check the box for
IN16, which is the Temperature Sensor Channel. - Check the box for
IN17, which is the Vrefint Channel.
- Check the box for
- Under
ADC1Parameter Settings, configure the following:Data Alignment: Right alignmentScan Conversion Mode: Enable (because we are now reading more than one channel).Continuous Conversion Mode: Disable.Number Of Conversions: 2.
- Expand the
Regular Conversion Modesection. You will see two Ranks (Rank 1 and Rank 2). Configure them as follows:- Rank 1: Channel =
Temperature Sensor, Sampling Time =71.5 Cycles(a longer sampling time is recommended for the temp sensor). - Rank 2: Channel =
VREFINT, Sampling Time =71.5 Cycles.
- Rank 1: Channel =
- Generate the code.
Code and Debugging
-
In
main.c, before thewhile(1)loop, declare the necessary variables. We'll use HAL's polling method to read both channels into an array./* USER CODE BEGIN 2 */ uint16_t adc_buf[2]; // To store results for Temp Sensor and VREFINT float temperature, vsense, vref_actual; // Datasheet values for STM32F103C8 const float AVG_SLOPE = 4.3f; // mV/°C const float V25 = 1.43f; // Volts const float VREFINT_TYP = 1.2f; // Volts, typical value from datasheet /* USER CODE END 2 */ -
Inside the
while(1)loop, implement the conversion logic./* USER CODE BEGIN WHILE */ while (1) { // Start ADC conversion for both channels. Polling will wait for both to complete. HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); adc_buf[0] = HAL_ADC_GetValue(&hadc1); // Get Temp Sensor result HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY); adc_buf[1] = HAL_ADC_GetValue(&hadc1); // Get VREFINT result HAL_ADC_Stop(&hadc1); // 1. Calculate the actual Vref using the VREFINT measurement // Vref_actual = VREFINT_TYP * (ADC_MAX_VALUE / ADC_VREFINT_RAW) vref_actual = (VREFINT_TYP * 4095.0f) / (float)adc_buf[1]; // 2. Convert the temperature sensor's raw ADC value to voltage (Vsense) vsense = (vref_actual / 4095.0f) * (float)adc_buf[0]; // 3. Calculate the temperature in Celsius temperature = ((V25 - vsense) * 1000.0f / AVG_SLOPE) + 25.0f; HAL_Delay(500); /* USER CODE END WHILE */Note: The code polls for each conversion individually. We will learn a much better way to handle multiple channels with DMA in an upcoming lesson.
-
Start a debug session. Add
temperature,vsense, andvref_actualto the Live Expressions window. -
Run the code. You should see a plausible room temperature reading. Try gently touching the top of the microcontroller chip with your finger for a few seconds. You should observe the
temperaturevalue rise slightly, proving your conversion is working!
Conclusion
Congratulations! You have successfully moved beyond raw data to meaningful physical measurements. This is a significant step toward building professional embedded systems that interact with the real world.
Key Takeaways:
- The fundamental formula to convert a raw ADC value to voltage is
Voltage = (ADC_Value / ADC_Max_Value) * Vref. - Relying on a fixed
Vrefis risky in real-world applications. The internalVREFINTprovides a robust way to measure the actual supply voltage and compensate for fluctuations. - Converting sensor data often requires specific formulas and calibration constants (
V25,Avg_Slope) that you must find in the device datasheet—a critical engineering skill. - By combining
VREFINTmeasurements with sensor-specific formulas, you can achieve accurate and reliable data acquisition.
Preview of the Next Lesson:
In our lab, we used HAL_ADC_PollForConversion, which forces the CPU to stop and wait for the ADC. This is highly inefficient. Imagine needing to read 5 different sensors while also managing a communication protocol. The CPU would be constantly blocked. In the next lesson, we will introduce the solution: the Direct Memory Access (DMA) controller. You will learn how this powerful peripheral can transfer ADC results directly to memory with zero CPU intervention, unlocking the true potential of your microcontroller.