STM32 Nucleo ADC with Polling, Interrupt and DMA STM32CubeIDE

In this STM32 Nucleo tutorial, we will learn to use ADC and read analog input voltage using STM32CubeIDE and HAL libraries. We will discuss three methods to read ADC including polling, interrupt, and DMA method. This will be demonstrated with the help of an LED dimmer example where we will connect the potentiometer as an analog input with one of the ADC pins of STM32 and measured digital values will be mapped to the PWM duty cycle which will control an LED brightness.

STM32 Nucleo ADC with Polling, Interrupt and DMA STM32CubeIDE

As discussed earlier, we will be using STM32CubeIDE, therefore, you should have the latest version installed on your system. You can follow this guide to install it:

STM32 Nucleo ADC Polling Method

As mentioned before, there are three ways of accessing ADC values. We will look at all three of them separately. Let us start with the first method which is known as polling. In this scenario, the ADC module is configured in blocking mode. When the ADC conversion initiates, the CPU stops until the ADC conversion is finished. After the ADC conversion completes, the CPU starts again and continues with the execution of the main code.

We will use STM32Cube IDE to program our STM32 board. Open the IDE and head over to a new project.

Then for the target selection, specify the STM32 Nucleo board number. After that click on any column as shown in the picture below. Then click the ‘Next’ button.

select STM32 Nucleo from board selector

Specify the name of your project then click ‘Finish’ to complete the setup of your project.

Firstly, we will set up an Analog Input Pin in a single Conversion Mode. This will be connected with the potentiometer. Head over to Analog and select ADC1. We have selected PA5 (channel 5) as the input pin and you can also view the parameter settings of the analog channel that we selected.

Next head over to Timers and select TIM2. The mode for Timer2 is set next where we configure the clock source as the internal clock and Channel 1 as PWM Generation CH1. This way Timer2 will work in PWM mode where PA0 (Timer2 Channel 1) will be set as an output.

Now go to System Core > RCC then select ‘Crystal/Ceramic Resonator’ from the High-Speed Clock feature.

Now we have enabled the RCC external clock source.

Clock Configuration

Next go to the Clock Configuration found at the top. This will open the following window. Here we will select the clock frequency.

STM32 Blue Pill UART Interrupt Clock

You can specify your system clock. We will set it as 72MHz. These are the configurations we have set. The ADC peripherals are assigned a default clock of 12MHz.

Now we will save our file. Press Ctrl + S. The following window will appear. Click ‘Yes.’ This will generate a template code for you.

Blue Pill STM32 using STM32Cube creating project pic 11

Another window will appear that will ask if you want to open the perspective. Click ‘Yes.’

Blue Pill STM32 Creating project Digital Input picture 11

ADC Read Polling Method with STM32 Nucleo Code

Now let us look at our main.c file that was generated.

We will set up a code in which the ADC is initialized at Channel 5 where a potentiometer is connected and the timer2 is configured to work in PWM mode with Channel 1 as an output where an LED will be connected. The ADC values will be converted to equivalent duty cycle values that will alter the brightness of the LED. However, in this case the ADC module will work in polling (blocking mode).

Inside the main.c file, look for the int main() function. Copy the code given below in the main() function.

int main(void)
{
  uint16_t AD_RES = 0;
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_TIM2_Init();

  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_ADCEx_Calibration_Start(&hadc1);

  while (1)
      {
          HAL_ADC_Start(&hadc1);
          HAL_ADC_PollForConversion(&hadc1, 1);
          AD_RES = HAL_ADC_GetValue(&hadc1);
          TIM2->CCR1 = (AD_RES<<4);
          HAL_Delay(1);
      }
}

Configure ADC in Polling Mode

The HAL_ADCEx_Calibration_Start() function is called to start an automatic calibration. This function takes in a parameter which is the pointer to a ADC_HandleTypeDef structure that contains the configuration information for the specified ADC. It is ‘&hadc1’ in our case.

HAL_ADCEx_Calibration_Start(&hadc1);

Inside the while loop, we call HAL_ADC_Start() to enable the ADC and start the conversion of the regular channels. It takes in a single parameter ‘hadc’ which is the pointer to a ADC_HandleTypeDef structure that contains the configuration information for the specified ADC. It is ‘&hadc1’ in our case.

HAL_ADC_Start(&hadc1);

Then we call HAL_ADC_PollForConversion() to poll for regular conversion. It takes in two parameters. The first parameter is the pointer to an ADC_HandleTypeDef structure that contains the configuration information for the specified ADC which is ‘&hadc1’ in our case. The second parameter is the timeout in milliseconds which we have specified as 1 millisecond.

HAL_ADC_PollForConversion(&hadc1, 1);

After that, the ADC conversion value from data register of regular channel is acquired by using HAL_ADC_GetValue() and mapped to the PWM duty cycle. The ADC conversion value is read and shifted to the timer CCR register which sets the PWM duty cycle percentage for the output LED pin.

 AD_RES = HAL_ADC_GetValue(&hadc1);
 TIM2->CCR1 = (AD_RES<<4);

Save the main.c file after modifying it. Now we are ready to build our project.

Building the Project

To build our project press Ctrl + B or go to Project > Build All.

Your project will start building. After a few moments, your project will be successfully built if there are no errors.

Hardware Setup for ADC LED Dimmer

STM32 Nucleo ADC with Polling, Interrupt and DMA STM32CubeIDE

For this ADC Read LED Dimmer project we will require the following components:

  1. STM32 Nucleo
  2. One 10k Potentiometer
  3. One 5mm LED
  4. One 220 ohm current limiting resistor
  5. Connecting Wires
  6. Breadboard

Now, connect the components as shown in the schematic diagram below:

Next, connect one terminal of the potentiometer with 3.3V to power it up, the center terminal with PA5, and the third terminal with common ground. Connect the LED’s anode pin to PA0 through a 220-ohm resistor. The cathode pin will be grounded. 

Demonstration

Rotate the knob of the potentiometer and the LED’s brightness will change as the ADC values increase/decrease.

STM32 Nucleo ADC Interrupt Method

In this section let us configure the ADC module using the interrupt method. In this scenario, the ADC module is configured in non-blocking mode where the ADC is triggered to start the ADC conversion. Unlike the polling method, the CPU does not stop but continues the execution of the main code. The completion of the ADC conversion marks the triggering of the interrupt. This enables the CPU to change a situation to the ISR handler and save the ADC conversion values. Using an interrupt method with a high rate of conversions can however overload the CPU.

To work using the interrupt method, create a new project in STM32 Cube IDE and follow similar steps as before.

  • Specify the target selection and then name of your project to complete the setup of your project.
  • Firstly, we will set up an Analog Input Pin in single Conversion Mode. This will be connected with the potentiometer. Head over to Analog and select ADC1. We have selected PA5 (channel 5) as the input pin. Head over to NVIC Settings under configuration and enable the ADC1 and ADC2 global interrupt.

The rest of the steps are same as we did previously.

  • Select Timer2 to work in PWM Mode and Output on Channel 1.
  • Now go to System Core > RCC then select ‘Crystal/Ceramic Resonator’ in from the High Speed Clock feature. Now we have enabled the RCC external clock source.
  • Next go to the Clock Configuration found at the top. This will open the following window. Here we will select the clock frequency. You can specify your system clock. We will set it as 72MHz. These are the configurations we have set. The ADC peripherals are assigned a default clock of 12MHz.

Now we will save our file. Press Ctrl + S. The following window will appear. Click ‘Yes.’ This will generate a template code for you.

Blue Pill STM32 using STM32Cube creating project pic 11

Another window will appear that will ask if you want to open the perspective. Click ‘Yes.’

Blue Pill STM32 Creating project Digital Input picture 11

ADC Read Interrupt Method with STM32 Nucleo Code

Now let us look at our main.c file that was generated.

We will set up a code in which the ADC is initialized at Channel 5 where a potentiometer is connected and the timer2 is configured to work in PWM mode with Channel 1 as an output where an LED will be connected. The ADC values will be converted to equivalent duty cycle values that will alter the brightness of the LED. However, in this case the ADC module is configured in interrupt mode (non-blocking mode).

Inside the main.c file, look for the int main() function. Copy the code given below in the main() function.

uint16_t AD_RES = 0;

int main(void)
{
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_TIM2_Init();

  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_ADCEx_Calibration_Start(&hadc1);

  while (1)
  {
      HAL_ADC_Start_IT(&hadc1);
      TIM2->CCR1 = (AD_RES<<4);
      HAL_Delay(1);
  }
}

Configure ADC in Interrupt Mode

The HAL_ADC_Start_IT() function is responsible for enabling the interrupt and starting ADC conversion of regular channels. It takes in a single parameter which is the pointer to the ADC_HandleTypeDef structure that contains the configuration parameters for the specified ADC. In our case it is ‘&hadc1.’

HAL_ADC_Start_IT(&hadc1);

The HAL_ADC_ConvCpItCallback() function is the regular conversion complete callback in non blocking mode. It also takes in the same parameter which is the pointer to the ADC_HandleTypeDef structure. Inside this callback, the ADC value is read and updated using HAL_ADC_GetValue().

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    AD_RES = HAL_ADC_GetValue(&hadc1);
}

Save the main.c file after modifying it. Now build and flash the project and it will give same results as with the polling method.

STM32 Nucleo ADC DMA Method

Lastly, we will show you how to configure the ADC module using the DMA method. In this scenario, the ADC module is configured in non-blocking mode where the DMA sends the ADC values from the peripheral to the memory directly. The CPU is not involved as was the case in polling and interrupt.

To configure ADC using the DMA unit, create a new project in STM32 Cube IDE and follow similar steps as before.

Specify the target selection and then name of your project to complete the setup of your project.

Firstly, we will set up an Analog Input Pin in single Conversion Mode. This will be connected with the potentiometer. Head over to Analog and select ADC1. We have selected PA5 (channel 5) as the input pin. Head over to DMA Settings and add the DMA request for the particular peripheral by clicking the Add button. Select ADC1. You can view the details off the DMA request by clicking the DMA that was just added. We have set the mode as ‘Normal.’ You can even set it as ‘Circular’ in which case the DMA continuously updates the data.

The rest of the steps are same as we did previously in polling and interrupt modes.

  • Select Timer2 to work in PWM Mode and Output on Channel 1.
  • Now go to System Core > RCC then select ‘Crystal/Ceramic Resonator’ in from the High Speed Clock feature. Now we have enabled the RCC external clock source.
  • Next go to the Clock Configuration found at the top. This will open the following window. Here we will select the clock frequency. You can specify your system clock. We will set it as 72MHz. These are the configurations we have set. The ADC peripherals are assigned a default clock of 12MHz.

Now we will save our file. Press Ctrl + S. The following window will appear. Click ‘Yes.’ This will generate a template code for you.

Blue Pill STM32 using STM32Cube creating project pic 11

Another window will appear that will ask if you want to open the perspective. Click ‘Yes.’

Blue Pill STM32 Creating project Digital Input picture 11

ADC Read DMA Method with STM32 Nucleo Code

Now let us look at our main.c file that was generated.

We will set up a code in which the ADC is initialized at Channel 5 where a potentiometer is connected and the timer2 is configured to work in PWM mode with Channel 1 as an output where an LED will be connected. The ADC values will be converted to equivalent duty cycle values that will alter the brightness of the LED. However, in this case, the ADC module is configured in DMA mode (non-blocking mode).

Inside the main.c file, look for the int main() function. Copy the code given below in the main() function.


uint16_t AD_RES = 0;

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_ADC1_Init();
    MX_TIM2_Init();
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);

    HAL_ADCEx_Calibration_Start(&hadc1);
 
    while (1)
    {
        HAL_ADC_Start_DMA(&hadc1, &AD_RES, 1);
        HAL_Delay(1);
    }
}

Configure ADC in DMA Mode

The HAL_ADC_Start_DMA() function is responsible for enabling the ADC DMA request after last transfer in Single-ADC mode and also enabling the ADC peripheral. It takes in three parameters. The first parameter is the pointer to the ADC_HandleTypeDef structure holding the configuration parameters for the specified ADC. The second parameter is the destination buffer address. The third parameter is the length of data that will be shifted from the ADC peripheral to memory.

HAL_ADC_Start_DMA(&hadc1, &AD_RES, 1);

The HAL_ADC_ConvCpItCallback() function is the regular conversion complete callback in non-blocking mode. It takes in a single parameter which is the pointer to the ADC_HandleTypeDef structure. At this point, the ADC conversion and DMA transfer finish, hence the destination buffer address gets updated. This ADC conversion value is mapped to the PWM duty cycle which determines the brightness of the LED.

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
   TIM2->CCR1 = (AD_RES<<4);
}

Save the main.c file after modifying it. Now build and flash the project and it will give the same results as with the polling and interrupt methods.

Conclusion

In conclusion, this STM32 Nucleo tutorial has covered how to use the ADC to read analog input voltage using STM32CubeIDE and HAL libraries. We explored three methods for reading ADC values: polling, interrupt, and DMA, each offering different advantages depending on the application’s needs. Through the LED dimmer example, we demonstrated how to connect a potentiometer as an analog input, read its value using the ADC, and map the digital output to control an LED’s brightness via PWM. This hands-on approach highlights the versatility of STM32 in handling analog signals and controlling peripherals efficiently.

You may also like to read:

Leave a Comment