Using the ADC

ADC

In this tutorial, we’ll learn how to use the ADC with STM32 Cube IDE.

Requirements

ADC? Like the band AC/DC?

The Analog-to-Digital Converter (ADC) is a system that, as the name suggests, converts an analog signal into a digital signal. The reverse process is done by a Digital-to-Analog Converter (DAC). This allows us to transform voltages into a format that the microcontroller can process and analyze. For example, this is how you can read the values from a potentiometer, a microphone, or a light sensor. If you want to explore this technology further, check out this webpage that explains ADCs in more depth (and no, I’m not sponsored!).

How it works

Analog/Digital signal
Red signal: Analog, Blue signal: Digital

  1. Sampling: The analog signal is sampled at regular time intervals (sampling frequency).
  2. Quantization: Each sampled value is approximated to the nearest discrete value in the binary range.
  3. Encoding: The quantized values are encoded into a sequence of bits. The resolution of the ADC, expressed in bits, determines how many quantization levels are possible. If we denote mathematical expression or equation as the number of bits, the number of levels is:
mathematical expression or equation

For example, on the STM32 board used in this tutorial (NUCLEO H7A3ZI-Q), the ADC is 12-bit: it can represent mathematical expression or equation levels (from 0 to 4095).

Configuration

Generally, on STM32 boards, pins A0 to A5 have ADC capability (but it’s always a good habit to check the datasheet!). We’ll do a simple test to check the minimum and maximum ADC levels.

To do this, connect one analog pin to +5V (here A0) and another to GND (here A1):

Wiring
Wiring

Now, in CubeIDE:

  • Create a new project and open CubeMX (.ioc window).

  • Identify which pins correspond to A0 and A1. You can find mappings between “Ax” and “PAx” by checking the pinout configuration of your board. In my case, A0 ↔ PA3 and A1 ↔ PC0.

  • Then configure each pin as ADC input (e.g., “ADCx_INPx”). My setup:

  • PA3: ADC1_INP15

  • PC0: ADC2_INP10

Now configure the pins with an ADC (like “ADCx_INPx”). Here’s the config I used:

  • PA3: ADC1_INP15
  • PC0: ADC2_INP10

Tip

Note that a pin may support multiple ADCs. I could’ve swapped ADC1 and ADC2 or even used ADC3. However, make sure not to configure the same ADC on both pins simultaneously!

In the “ADC” tab on the left, select ADC1 and configure as follows:

Configuration

  • Check IN15 Single-ended
  • Set Resolution to ADC 12-bit resolution

Tip

If you get clock frequency errors, go to the “Clock configuration” tab and tweak the values of DIVM2, DIVN2, and DIVP2 to fix them.

Repeat for ADC2.

Programming

Here’s how to program ADC1 in a .c file. Programming ADC2 or any other is done in a similar way.

To start ADC conversion, add the two lines below to your main.c file (in the “Core/Src” folder):

To start ADC conversion, add the lines below in your main.c file (in the “Core/Src” folder):

Inside the private variables section:

main.c
43/* Private variables ---------------------------------------------------------*/
44
45UART_HandleTypeDef huart2;
46
47/* USER CODE BEGIN PV */
48int value_a0;
49/* USER CODE END PV */

Inside the int main(void){} function:

main.c
65int main(void)
66{
67  /* USER CODE BEGIN 1 */
68  HAL_ADC_Start(&hadc1);
69  /* USER CODE END 1 */

HAL_ADC_Start(&hadc1); starts conversion for ADC1. int value_a0 is used to store the value read on the A0 pin.

Warning

Be sure to write your code between the “/* USER CODE BEGIN */” and “/* USER CODE END */” tags. Otherwise, your code will be deleted when building!

Inside a loop (e.g. the existing while(1){}), add the two lines below:

HAL_ADC_PollForConversion(&hadc, 1); waits for ADC conversion to complete. value_a0 = HAL_ADC_GetValue(&hadc1); retrieves the converted value and stores it in value_a0.

main.c
 99/* Infinite loop */
100/* USER CODE BEGIN WHILE */
101while (1)
102{
103  HAL_ADC_PollForConversion(&hadc, 1);
104  value_a0 = HAL_ADC_GetValue(&hadc1);
105  /* USER CODE END WHILE */
106
107  /* USER CODE BEGIN 3 */
108}

Once compiled and uploaded to your board, if you display the values from A0 (value_a0) and A1 (value_a1) using the above method, you’ll get value_a0 = 4095 and value_a1 = 0, which matches the resolution. If you go further, you can send these values to a serial plotter to visualize the signal:

Serial plotter
Blue signal: A0, Orange signal: A1

To go further, you can try connecting connecting other components to these pins, like a potentiometer as shown in the introduction to embedded programming

Interpreting ADC values using sensitivity

If you want to convert ADC readings into human-readable voltages instead of LSBs (Least Significant Bit), you’ll need the ADC sensitivity. Use the formula:

mathematical expression or equation

Where:

  • range: Voltage range (e.g., 0 to 5V)
  • resolution: Bit resolution of your ADC

In my case, my voltage values range from 0 to 5V for a 12-bit ADC. So range = 5V, resolution = 12 bits, that gives us:

mathematical expression or equation

Then you can multiply this sensitivity by your ADC value to get a voltage reading:

  • For 0 LSB: mathematical expression or equation
  • For 4095 LSB: mathematical expression or equation
  • For 1024 LSB: mathematical expression or equation

Credits