Controlling Servo Motor using STM32 Microcontroller: Example Code Included

Welcome to our comprehensive guide on controlling a servo motor using the STM32 microcontroller. In this tutorial, we’ll delve into the fundamental workings of servo motors and demonstrate how to manipulate them using PWM signals generated by an STM32 microcontroller. To help you grasp these concepts, we’ll build an example project on the STM32 Nucleo Development Board using the STM32CubeIDE.

We have previously published a tutorial on generating PWM signals with the STM32 Microcontroller. Please ensure you read that tutorial before beginning this one.

Understanding Servo Motors

The functionality of servo motors relies on a feedback system that ensures accurate positioning. At the heart of a servo motor are a small DC motor, a control circuit, a potentiometer (used for positional feedback), and a gear system for torque enhancement.

The control circuit receives a PWM signal that dictates the desired position. It then uses the potentiometer’s feedback to adjust the motor’s position until it matches the command, ensuring precise movement control.

Servo motors fall into two main categories: rotary and linear. Rotary servo motors, which are capable of turning in a range of 0 to 180 degrees, 360 degrees, or even continuous rotation, depending on the model, are commonly found in robotic joints, camera tracking systems, and RC vehicles.

On the other hand, linear servo motors convert rotary motion into linear motion and are used in devices like 3D printers and CNC machines.

In this tutorial, we will interface 180-degree rotary servo motors with STM32 Microcontroller.

Servo Motor

How to control a Servo Motor

To precisely control a servo motor, the technique of Pulse Width Modulation (PWM) is employed, where the position of the servo’s arm is determined by the length of time the signal remains high.

For the servo to reach a position at the start of its range, the PWM signal stays high for 1 millisecond, indicating a 0-degree orientation.

servo 0 degree
0-degree servo orientation

For a midpoint orientation at 90 degrees, the duration of the high signal is extended to 1.5 milliseconds.

To achieve the maximum range of motion at 180 degrees, the signal’s high phase lasts for 2 milliseconds.

servo 180 degree
180-degree servo orientation

These control signals are sent with a repetition rate that maintains a 20-millisecond gap between pulses, equivalent to a control frequency of 50 Hz, ensuring the servo motor operates smoothly and maintains its positions with precision.

Please Note: Sometimes we found that some of the servo Motors were not rotating the full 180 degrees at 2 milliseconds as they should. So, you may need to test the servo using oscilloscopes or logic analyzer at which duty cycle servo rotates full 0 to 180 degrees. In some cases from 0.5 ms to 2.5 ms, it worked well.

Controlling Servo Motor using STM32 Microcontroller: The Project

In this section of the tutorial, we will build a small project by using the STM32F446RE NUCLEO Board and a Servo Motor in STM32CubeIDE. We will use a small 180-degree servo motor and rotate it 0 to 180 degrees clockwise and 180 to 0 degrees counter clockwise continuously.

Controlling Servo Motor using STM32 Microcontroller

Component List

Component NameQuantityPurchase Link
STM32 Development Board1Amazon | Amazon
Servo Motor1Amazon
Jumper Wire Set1Amazon
5V Power Supply1Amazon

For troubleshooting, some extremely useful test equipment

Equipment NamePurchase Link
Best Oscilloscope for ProfessionalsAmazon
Best Oscilloscope for Beginners and HobbyistsAmazon
Logic AnalyzerAmazon
Best Budget MultimeterAmazon
Adjustable Bench Power SupplyAmazon

Circuit Diagram

circuit diagram

Please Note: Power the servo motor using an external power supply. In this circuit, we use STM32F446RE NUCLEO Board internal power supply which is not a good practice for a professional project.

Pin Connections

STM32 NUCLEO BoardServo Motor
+5V (please use external power supply)Red Wire
GroundBrown Wire
Port A Pin 0 (PA0)Yellow Wire

Preparing STM32CubeIDE

To create a project in Stm32CubeIDE, please visit our previous tutorial. The link is given below:

After creating the STM32CubeIDE project, from the CubeMX perspective open the Clock Configuration setting and select the clock source (HSI or HSE). We selected the Internal clock source (HSI) of 16 MHz and generated an 84MHz clock using the PLL of the STM32 microcontroller for this example project.

We use the Timer 2 peripheral of STM32F445RE MCU which is a part of the APB1 Timer Clock. We also configure the APB1 Timer Clock as 84 MHz. The Clock Configuration image is given below.


After that click on Pinout & Configuration tab and select your Timer. We use Timer 2 (TIM2) for this servo project. Then go to the Mode section of Timer 2 and select Clock Source as Internal Clock and Channel as PWM generation CH1.

Also, select the Port and Pin where you want to generate the PWM signal. In our case, we use Port A Pin 0 (PA0) for Timer 2 PWM generation Channel 1.


After that click on Parameters Settings and change the Prescaler and Period value according to your requirements. We set the prescaler as 84 to configure the Timer clock of 1MHz.

So, Timer Clock Speed = 84MHz/84 = 1MHz

Also, we set the Counter Period (AutoReload Register)  to 20000. It means when the Counter Period reaches 20000 the PWM duty cycle will be 100% with a period of 20 milliseconds or 50 Hz. frequency.

So, Frequency = 1000000 Hz/20000 = 50Hz

Please Note: For both the parameters, we add -1 because the Prescalar Register and the AutoReload Register of STM32 already add 1 in their register setup.


Code Explanation

void setServoAngle(uint32_t angle) {
   if (angle > 180) angle = 180;
   uint32_t minPulseWidth = 1000; // 1ms pulse width at a 1MHz clock
   uint32_t maxPulseWidth = 2000; // 2ms pulse width
   uint32_t pulse = ((angle * (maxPulseWidth - minPulseWidth)) / 180) + minPulseWidth;
   __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse); // Changed to TIM2, Channel 1

The setServoAngle(uint32_t angle) function is designed to control the position of a servo motor by generating a corresponding PWM signal based on the specified angle as a parameter.

It first ensures the angle does not exceed 180 degrees, then calculates the appropriate PWM pulse width by linearly interpolating between the predefined minimum (1 millisecond) and maximum (2 milliseconds) pulse widths based on a 0 to 180-degree range.

linear interpolation formula:

uint32_t pulse = ((angle * (maxPulseWidth - minPulseWidth)) / 180) + minPulseWidth;

This pulse width calculation is used to set the PWM duty cycle via the __HAL_TIM_SET_COMPARE() function, which adjusts the output on a timer channel (TIM2, Channel 1) to control the servo’s mechanical position accurately. 

Full Code

/* USER CODE BEGIN Header */
  * @file           : main.c
  * @brief          : Main program body
  * @attention
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private variables ---------------------------------------------------------*/
TIM_HandleTypeDef htim2;

UART_HandleTypeDef huart2;

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_TIM2_Init(void);


/* Private user code ---------------------------------------------------------*/
void setServoAngle(uint32_t angle) {
    if (angle > 180) angle = 180;
    uint32_t minPulseWidth = 1000; // 1ms pulse width at a 1MHz clock
    uint32_t maxPulseWidth = 2000; // 2ms pulse width
    uint32_t pulse = ((angle * (maxPulseWidth - minPulseWidth)) / 180) + minPulseWidth;
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse); // Changed to TIM2, Channel 1

  * @brief  The application entry point.
  * @retval int
int main(void)
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  /* USER CODE END 2 */

  /* Infinite loop */
  while (1)

    /* USER CODE BEGIN 3 */
	  // Move from 0 to 180 degrees
		for (uint32_t angle = 0; angle <= 180; angle++) {
			 HAL_Delay(10); // Adjust delay for speed control
		// Move from 180 to 0 degrees
		for (uint32_t angle = 180; angle > 0; angle--) {
			 HAL_Delay(10); // Adjust delay for speed control
  /* USER CODE END 3 */

Please Note: Certain portions of the code, specifically those automatically generated by STM32CubeIDE, have been omitted.

Mahamudul Hasan

I hold a B.Sc degree in Electrical & Electronic Engineering from Daffodil International University, Bangladesh. I am a Printed Circuit Board (PCB) designer and Microcontroller programmer with an avid interest in Embedded System Design and IoT. As an Engineer, I love taking challenges and love being part of the solution. My Linkedin Profile Link:

Recent Posts