Hobby servo motors are nifty and useful for a wide range of projects. While originally used in remote-controlled cars and airplanes, they now crop up in all sorts of other applications due to their precise positioning capabilities. Simply instruct them where to point, and they’ll do it for you.
In this tutorial, you will learn how to control a servo motor with an ESP32. We’ll start by making the servo sweep back and forth automatically. Then, we’ll introduce a potentiometer to manually control the servo’s position. Finally, we’ll expand the project to control multiple servo motors simultaneously. To begin, we’ll cover some basics of servo motors. However, if you’re already familiar with these basics, feel free to jump ahead to the hardware hookup section.
Let’s get started!
Servo Motor Basics
Inside a typical hobby servo motor, you’ll find five main components working together: a DC motor, a gearbox, a servo horn, a potentiometer, and a control unit.

DC Motor: This is the core component providing the rotational force. It’s similar to the hobby motors found in toys, but how it’s controlled makes all the difference. The DC motor connects to the output shaft through a series of gears.
Gearbox: The gearbox (a system of gears) plays a crucial role in increasing the motor’s torque and improving the precision of its movements. It reduces the speed of the motor but increases its strength.
Servo Horn: This is a plastic arm (or wheel) attached to the output shaft (the part that actually rotates). The servo horn gives you a place to attach whatever you want the servo to move – like the arm of a robot or the rudder on a model airplane.
Potentiometer: This is a variable resistor that acts as a position sensor. It’s connected directly to the output shaft. As the output shaft rotates, the potentiometer’s resistance changes. This tells the control unit exactly where the motor is positioned.
Control Unit: This is the “brain” of the servo motor. It receives signals from an external controller (like an ESP32), checks the potentiometer’s position, and controls the DC motor.
Here’s how these parts work together:
The servo motor works using what’s called a “closed-loop feedback system“.

When you send a signal to the servo telling it to move to a specific position, the control unit receives this command.
The potentiometer is physically connected to the output shaft, so it always knows the current position of the servo. When the motor rotates, the output shaft turns, and so does the potentiometer. This rotation changes the potentiometer’s resistance, creating a voltage that directly corresponds to the current position of the output shaft (and thus the motor). This voltage serves as feedback to the control unit.
The control unit constantly compares this feedback voltage (representing the motor’s current position) with the desired position signal from the microcontroller.
If there’s a difference (an “error” signal) between the desired position and the current position, the control unit adjusts the power and direction of the DC motor to fix this difference.
As the motor moves, the potentiometer’s feedback voltage changes, continuously updating the control unit about the motor’s current position.
Once the motor reaches the desired position you wanted, the error signal becomes zero, and the control unit stops the motor.
This whole process happens very quickly and repeatedly. The servo is always checking its position and making tiny adjustments to stay exactly where it should be. This is why servos are so good at holding their position, even when external forces try to move them.
This entire system is called a “servomechanism” or simply a “servo.” It works as a closed-loop control system, using negative feedback to precisely control the motor’s speed and direction, allowing it to reach and maintain a specific position.
How Do Servo Motors Work?
Hobby servo motors are controlled using a clever technique called Pulse Width Modulation (PWM).
PWM works by sending a series of electrical pulses to the servo at regular intervals. For most hobby servos, these pulses are sent about 50 times per second (50 Hz). This means a new pulse arrives every 20 ms (1/50th of a second).
What really matters for controlling the servo’s position isn’t how often we send pulses, but how long each pulse lasts. This duration is called the “pulse width” (or “duty cycle” of the PWM signal, if you prefer the technical term).

- A short pulse around 1 ms or less tells the servo to move to 0 degrees (all the way to one side)
- A medium pulse of about 1.5 ms positions the servo at 90 degrees (right in the middle)
- A longer pulse of around 2 ms moves the servo to 180 degrees (all the way to the other side)
- For positions in between, we use pulse widths that fall between these values. For example, if we want the servo at the 45-degree position (halfway between 0 and 90 degrees), we would use a pulse that’s about 1.25 ms long.
This means we can precisely control exactly where the servo points by carefully controlling how long each pulse lasts. The servo’s control unit receives these pulses, measures their width, and moves the motor to the corresponding position.
The animation below can help you visualize how changes in pulse width correspond to different servo positions.

Not All Servos Are Exactly the Same
It’s important to note that the exact pulse width range can vary slightly between different servo motor models. Some servos might use a range such as 0.5 ms for 0 degrees and 2.5 ms for 180 degrees.
This is why it’s always a good idea to check the datasheet that comes with your specific servo motor. The datasheet will tell you exactly what pulse widths to use for different positions with your particular servo.
Servo Motor Connections
Almost all hobby servos come with a standard three-pin, 0.1″-spaced connector. While the color coding of the wires may vary between brands, the pins are generally arranged in the same order.
Here’s a breakdown of the pinout:

GND is the ground pin.
5V pin supplies power to the servo motor. Most hobby servos need between 4.8V and 6V to operate properly. Providing the right voltage is important – too little and the servo won’t have enough strength, too much and you might damage it.
Control receives the PWM (Pulse Width Modulation) signal from a microcontroller, which determines the servo’s position.
The color coding of the wires can vary between brands, but the power wire is almost always Red, making it easy to identify. The ground wire is typically black or brown, and the control wire is usually orange, yellow, or white.
Connecting the Servo Motor to the ESP32
Alright! Let’s connect a servo motor to the ESP32.
For this experiment, we’ll use an SG90 Micro Servo Motor. This small but powerful motor runs on 5V (can work between 4.8V to 6V DC) and can rotate up to 180 degrees—90 degrees in each direction.
It’s important to note that when the servo is sitting still, it only needs about 10mA of current. But when it’s moving, it needs much more—between 100mA and 250mA. This means that for most simple projects, we can power the servo directly from the ESP32’s VIN pin. But if your servo needs more than 250mA, you should use a separate power supply to avoid damaging your ESP32.
To connect the SG90 servo motor to the ESP32:
- Connect the red wire to the VIN pin on the ESP32.
- Connect the black or brown wire to the GND (ground) pin.
- Connect the orange or yellow wire to GPIO13 on the ESP32. Actually, you can use any ESP32 GPIO, as each one is capable of producing a PWM signal. However, it is advisable to avoid using GPIOs 9, 10, and 11 because they are connected to the integrated SPI flash and are not recommended for other purposes. Please refer to the ESP32 Pinout Reference for more information.
Here’s a quick reference table for the pin connections:
| Servo Motor | ESP32 | |
| 5V | VIN | |
| GND | GND | |
| Control | GPIO13 |
Please refer to the image below to see the proper wiring setup.

Setting Up the Arduino IDE
We will be using the Arduino IDE to program the ESP32, so please ensure you have the ESP32 add-on installed before you proceed:
Library Installation
The Arduino IDE comes with a built-in Servo library for controlling servo motors, but unfortunately it’s not compatible with the ESP32. However, there are several libraries available specifically for the ESP32, many of which emulate the Arduino Servo library while adding extra functionality.
For this tutorial, we’ve chosen to use the ESP32Servo Library by Kevin Harrington. This library not only replicates the original Arduino Servo library’s features but also introduces some additional capabilities.
To install the library:
- First open your Arduino IDE program. Then click on the Library Manager icon on the left sidebar.
- Type “ESP32Servo” in the search box to filter your results.
- Look for the “ESP32Servo” library created by Kevin Harrington.
- Click the Install button to add it to your Arduino IDE.

Example 1: Making a Servo Sweep
The first example sweeps the shaft of a servo motor back and forth across 180 degrees. Go ahead and upload the sketch.
#include <ESP32Servo.h>
// create servo object to control a servo
Servo myservo;
// variable to store the servo position
int pos = 0;
// GPIO pin used to connect the servo
// Recommended PWM GPIO pins on the ESP32 include 2,4,12-19,21-23,25-27,32-33
int servoPin = 13;
void setup() {
// Allow allocation of all timers
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
// Standard 50hz servo
myservo.setPeriodHertz(50);
// attach the servoPin to the servo object and set the sweep range
// For SG90, 500 and 2400 might be more suitable
// different servos may require different min/max settings
myservo.attach(servoPin, 500, 2400);
}
void loop() {
// goes from 0 degrees to 180 degrees
for (pos = 0; pos <= 180; pos += 1) {
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
// goes from 180 degrees to 0 degrees
for (pos = 180; pos >= 0; pos -= 1) {
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}
}After uploading the sketch to your ESP32 and connecting the hardware, you should observe the servo moving in a sweeping motion. However, you might notice that the servo doesn’t achieve a full 180-degree sweep. This is because each servo motor can vary slightly. To adjust the sweep range, modify the minimum and maximum time values in the sketch. Through experimentation, you can determine the optimal values for your specific motor. For instance, with an SG90 servo, values of 500 and 2400 might be more suitable.
Code Explanation:
First, the ESP32Servo.h library is included. This library provides functions for controlling servo motors on the ESP32.
#include <ESP32Servo.h>An object called myservo from the Servo class is then created. This object represents the servo motor you’ll be controlling. You can create up to 16 such objects on the ESP32.
Servo myservo;In that same global area, two variables are defined: one for storing the current position of the servo in degrees (pos) and another for specifying the GPIO pin to which your servo motor’s signal wire is connected (servoPin).
int pos = 0;
int servoPin = 13;In the setup function, you’ll notice several lines related to timer allocation. This is a necessary step when using the ESP32Servo library because the ESP32 microcontroller relies on timers to generate the PWM signals that control servo motors.
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);The frequency of the PWM signal is then set to 50 Hz (Hertz), which is the standard frequency for most hobby servos. However, you can play with this value to improve the performance of your specific servo motor.
myservo.setPeriodHertz(50);Next, the myservo.attach() method is used to link the servo object to the actual GPIO pin (servoPin) on the ESP32. This method also sets the minimum and maximum pulse widths (in microseconds) that determine the servo’s range of motion. In this example, the minimum pulse width is set to 500 microseconds, corresponding to the 0-degree position, while the maximum pulse width is set to 2400 microseconds, representing the 180-degree position. These values might need adjustment depending on the specific servo motor you’re using.
myservo.attach(servoPin, 500, 2400);The loop() function contains two for loops. The first loop sweeps the servo from 0 to 180 degrees. Inside this loop, the myservo.write(pos) method updates the servo’s position to the current value of the pos variable, which increments by one degree in each iteration. After each update, the delay(15) function pauses the program for 15 milliseconds to give the servo enough time to reach the specified position.
for (pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// in steps of 1 degree
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}The second loop sweeps the servo back from 180 degrees to 0 degrees, again updating the servo’s position and waiting 15 milliseconds for each degree.
for (pos = 180; pos >= 0; pos -= 1) { // goes from 180 degrees to 0 degrees
myservo.write(pos); // tell servo to go to position in variable 'pos'
delay(15); // waits 15ms for the servo to reach the position
}Example 2: Controlling Servo with a Potentiometer
The next example involves including a potentiometer so that we can manually adjust the servo’s position. This project can be extremely helpful when controlling the pan and tilt of a sensor connected to a servo.
Wiring
We’ll reuse the wiring from the previous example, but this time we’ll add a 10KΩ potentiometer. Connect one end of the pot to ground, the other to VIN, and the wiper to GPIO34.

Arduino Code
The code for making the servo follow the position of the knob is simpler than the code for making it sweep.
#include <ESP32Servo.h>
// create servo object to control a servo
Servo myservo;
// GPIO pin used to connect the servo
// Recommended PWM pins on the ESP32 include 2,4,12-19,21-23,25-27,32-33
int servoPin = 13;
// GPIO pin used to connect the potentiometer
// Recommended ADC pins on the ESP32 include 0,2,4,12-15,32-39; 34-39 are recommended for analog input
int potPin = 34;
// Variable to store the value from the analog pin
int val;
void setup() {
// Allow allocation of all timers
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
// Standard 50hz servo
myservo.setPeriodHertz(50);
// attach the servoPin to the servo object and set the sweep range
// For SG90, 500 and 2400 might be more suitable
// different servos may require different min/max settings
myservo.attach(servoPin, 500, 2400);
}
void loop() {
val = analogRead(potPin); // read the value of the potentiometer (0 to 4096 for 12-bit ADC)
val = map(val, 0, 4096, 0, 180); // scale it to use it with the servo (0 to 180)
myservo.write(val); // set the servo position according to the scaled value
delay(200); // wait for the servo to reach the position
}Code Explanation
Notice that two new variables have been introduced: one for specifying the GPIO pin to which potentiometer is connected (potPin) and another for storing the value from the analog pin (val).
int potPin = 34;
int val;We start the loop function by reading the value from potPin.
val = analogRead(potPin);The analogRead() function returns a value between 0 and 4096. However, we must scale it down because the servo can only rotate 180 degrees.
One method is to use the Arduino map() function, which remaps a number from one range to another. The line below converts the reading to degrees between 0 and 180.
val = map(val, 0, 4096, 0, 180);Finally, we use the write() command to update the servo’s position to the angle selected by the potentiometer.
myservo.write(val);Example 3: Controlling Multiple Servos
The ESP32 has 16 PWM channels, which means you can directly control up to 16 servo motors. The ESP32Servo library that we are using also supports all 16 PWM pins, making it easy to add more servos to your project.
Wiring
Let’s add another servo motor to your circuit. However, instead of powering it through the ESP32’s VIN pin, we’ll use a separate power supply. This ensures the servo motors receive enough current. Connect the signal pin of the new servo motor to GPIO14 on the ESP32.
The image below shows how to connect everything.

Arduino Code
Now, let’s modify the previous code example to make both servo motors to sweep back and forth between 0 and 180 degrees independently.
#include <ESP32Servo.h>
// create two servo objects
Servo servo1;
Servo servo2;
int servo1Pin = 13;
int servo2Pin = 14;
int pos = 0; // position in degrees
ESP32PWM pwm;
void setup() {
// Allow allocation of all timers
ESP32PWM::allocateTimer(0);
ESP32PWM::allocateTimer(1);
ESP32PWM::allocateTimer(2);
ESP32PWM::allocateTimer(3);
servo1.setPeriodHertz(50); // Standard 50hz servo
servo2.setPeriodHertz(50); // Standard 50hz servo
servo1.attach(servo1Pin, 500, 2400);
servo2.attach(servo2Pin, 500, 2400);
}
void loop() {
// sweep 1st servo from 0 degrees to 180 degrees
for (pos = 0; pos <= 180; pos += 1) {
servo1.write(pos);
delay(15);
}
// sweep 2nd servo from 0 degrees to 180 degrees
for (pos = 0; pos <= 180; pos += 1) {
servo2.write(pos);
delay(15);
}
// sweep 1st servo from 180 degrees to 0 degrees
for (pos = 180; pos >= 0; pos -= 1) {
servo1.write(pos);
delay(15);
}
// sweep 2nd servo from 180 degrees to 0 degrees
for (pos = 180; pos >= 0; pos -= 1) {
servo2.write(pos);
delay(15);
}
}Code Explanation:
Let’s go through the changes and new additions in this code compared to the first one:
Instead of a single servo object, two servo objects, servo1 and servo2, are now created.
Servo servo1;
Servo servo2;Two constants, servo1Pin and servo2Pin, are defined and set to 13 and 14 respectively. These represent the GPIO pins on the ESP32 to which the two servos are connected.
int servo1Pin = 13;
int servo2Pin = 14;Both servo1 and servo2 are set to the standard servo frequency of 50 Hz using the setPeriodHertz() method. The attach() method links both servo1 and servo2 objects to their respective GPIO pins on the ESP32. The values 500 and 2400 are then provided as arguments to specify the minimum and maximum pulse widths for both servos, which determine the range of motion from 0 to 180 degrees.
servo1.setPeriodHertz(50);
servo2.setPeriodHertz(50);
servo1.attach(servo1Pin, 500, 2400);
servo2.attach(servo2Pin, 500, 2400);The loop() function consists of four for loops. The first loop sweeps servo1 from 0 to 180 degrees. The second loop sweeps servo2 from 0 to 180 degrees. The third loop sweeps servo1 back from 180 to 0 degrees. The fourth loop sweeps servo2 back from 180 to 0 degrees.
Just as before, for each degree of movement, the position of the servo is updated using the write() method, and a 15 millisecond delay allows the servo to reach its target position.
// sweep 1st servo from 0 degrees to 180 degrees
for (pos = 0; pos <= 180; pos += 1) {
servo1.write(pos);
delay(15);
}
// sweep 2nd servo from 0 degrees to 180 degrees
for (pos = 0; pos <= 180; pos += 1) {
servo2.write(pos);
delay(15);
}
// sweep 1st servo from 180 degrees to 0 degrees
for (pos = 180; pos >= 0; pos -= 1) {
servo1.write(pos);
delay(15);
}
// sweep 2nd servo from 180 degrees to 0 degrees
for (pos = 180; pos >= 0; pos -= 1) {
servo2.write(pos);
delay(15);
}Going Further
Want to take your servo control to the next level? You can leverage the ESP32’s built-in Wi-Fi capabilities to develop a web interface. This allows you to adjust the servo’s position remotely from any device with a web browser. This opens up a ton of cool project ideas, like building robot arms you can steer remotely, smart home gadgets that open blinds or feed your pets, or even lifelike animatronics with movements you can control from afar.



