If you have ever watched a robotic arm pick up an object, a camera pan smoothly across a room, or an RC car steer around a corner, you have seen servo motors at work. They are the muscle behind precise angular movement in electronics projects, and learning to control them is one of the most rewarding skills you can pick up as a maker.
In this tutorial, we will go from understanding how servos work internally all the way to writing production-quality Arduino code for smooth, multi-servo coordination. Every code example is tested, practical, and ready to upload.
What Is a Servo Motor?
A servo motor is a rotary actuator that allows precise control of angular position. Unlike a regular DC motor that just spins, a servo moves to a specific angle and holds it there.
Inside the Housing
Every hobby servo contains three key systems working together:
- DC Motor -- The core driver that produces rotation.
- Gear Train -- A set of reduction gears that trade speed for torque. This is why servos are slow but strong relative to their size. Metal gears (as found in the MG90S) handle higher loads without stripping.
- Control Circuit with Feedback Loop -- A potentiometer attached to the output shaft tells the control board the current position. The board compares this to the desired position (from your signal) and drives the motor until the error is zero.
This closed-loop feedback system is what makes a servo a servo. It continuously corrects its position, which is why servos hold their angle firmly even under load.
The PWM Control Signal
Servos are controlled using a Pulse Width Modulation (PWM) signal, but not in the way PWM is used for LED dimming. The servo reads the width of a pulse sent every 20 milliseconds (50 Hz):
| Pulse Width | Servo Position |
|---|---|
| ~544 us | 0 degrees |
| ~1500 us | 90 degrees (centre) |
| ~2400 us | 180 degrees |
The servo ignores the frequency to some extent -- what matters is the duration of each HIGH pulse within the 20 ms window. The Arduino Servo library handles all of this timing for you, so you simply call servo.write(90) and it generates the correct pulse.
Types of Servo Motors
Not all servos are the same. Choosing the right one depends on your project requirements.
Micro Servos (SG90, MG90S)
These are the most popular servos for hobby projects. They weigh 9-14 grams and fit almost anywhere.
- SG90 -- Plastic gears, 1.2 kg-cm torque. Good for lightweight tasks like sensor panning. Affordable but the gears strip under stress.
- MG90S -- Metal gears, 1.8 kg-cm torque. The upgrade pick. Handles more load, lasts longer, and costs only slightly more. This is the servo we recommend for most beginner-to-intermediate projects.
Standard Servos
Larger servos like the MG995 or MG996R deliver 10-13 kg-cm of torque. Used in robotic arms, RC cars, and anything that needs real holding power. They draw significantly more current (up to 2A under load), so dedicated power supplies are mandatory.
Continuous Rotation Servos
These have had their feedback potentiometer removed or replaced. Instead of moving to an angle, write(0) spins full speed one way, write(180) spins full speed the other way, and write(90) stops. Useful for wheeled robots, but if you need position control, stick with standard servos.
MG90S Specifications
Since the MG90S is the most practical all-rounder for projects, here are its full specs:
| Parameter | Value |
|---|---|
| Gear Type | Metal |
| Rotation | 0 to 180 degrees |
| Stall Torque | 1.8 kg-cm (at 4.8V), 2.2 kg-cm (at 6V) |
| Operating Voltage | 4.8V -- 6.0V |
| Speed | 0.1 sec/60 degrees (at 4.8V) |
| Weight | ~13.4 grams |
| Dimensions | 22.5 x 12 x 35.5 mm |
| Signal | PWM, 50 Hz |
| Wire Colors | Orange (signal), Red (VCC), Brown (GND) |
The metal gears make a significant difference. In projects with any mechanical load -- a pan-tilt bracket, a gripper, a latch -- plastic gears will eventually strip. The MG90S handles repeated stress without failure.
Wiring a Servo to Arduino
Servo wiring is straightforward. There are only three wires:
- Orange (or White) -- Signal. Connect to any PWM-capable digital pin on the Arduino (pins 9 and 10 are conventional).
- Red -- VCC. Connect to 5V power.
- Brown (or Black) -- Ground. Connect to GND.
Single Servo Wiring (Powered from Arduino)
For a single micro servo doing light work, you can power it directly from the Arduino 5V pin:
Arduino Pin 9 ------> Servo Signal (Orange)
Arduino 5V ------> Servo VCC (Red)
Arduino GND ------> Servo GND (Brown)
This works for testing but has limits. The Arduino 5V pin can supply roughly 500 mA, and a servo under load can briefly draw 300-700 mA. For anything beyond a single unloaded servo, use an external supply.
Multiple Servos (External Power Required)
Arduino Pin 9 ------> Servo 1 Signal (Orange)
Arduino Pin 10 ------> Servo 2 Signal (Orange)
Arduino GND ------> Common GND bus
External 5V PSU (+) ------> Servo 1 VCC (Red)
External 5V PSU (+) ------> Servo 2 VCC (Red)
External 5V PSU (-) ------> Common GND bus
Critical: Connect the Arduino GND to the external supply GND. Without a common ground, the signal has no reference and the servos will behave erratically.
Arduino Servo Library Basics
The built-in Servo library makes servo control trivial. It is included with every Arduino IDE installation.
#include <Servo.h>
Servo myServo;
void setup() {
myServo.attach(9); // Attach to pin 9
}
void loop() {
myServo.write(90); // Move to 90 degrees
delay(1000);
}
Key Methods
| Method | Description |
|---|---|
attach(pin) |
Attach servo to a digital pin |
attach(pin, min, max) |
Attach with custom pulse width range (in microseconds) |
write(angle) |
Move to angle (0-180) |
writeMicroseconds(us) |
Direct pulse width control for finer precision |
read() |
Returns the last angle written |
attached() |
Returns true if servo is attached |
detach() |
Release the pin (stops sending pulses, servo goes limp) |
Code Example 1: Basic Sweep
The classic first servo program. The servo sweeps from 0 to 180 degrees and back, continuously.
#include <Servo.h>
Servo myServo;
void setup() {
myServo.attach(9);
}
void loop() {
// Sweep from 0 to 180
for (int angle = 0; angle <= 180; angle++) {
myServo.write(angle);
delay(15); // 15ms per degree = ~2.7 seconds for full sweep
}
// Sweep back from 180 to 0
for (int angle = 180; angle >= 0; angle--) {
myServo.write(angle);
delay(15);
}
}
The delay(15) controls the speed. Smaller values make the servo move faster; larger values make it slower and smoother. Values below 10 ms may cause the servo to skip steps since it needs time to physically reach each position.
Code Example 2: Potentiometer Control
This maps a potentiometer's analog reading to the servo angle, giving you real-time manual control. This is the foundation for any joystick or knob-controlled mechanism.
#include <Servo.h>
Servo myServo;
const int SERVO_PIN = 9;
const int POT_PIN = A0;
void setup() {
myServo.attach(SERVO_PIN);
Serial.begin(9600);
}
void loop() {
int potValue = analogRead(POT_PIN); // 0 to 1023
int angle = map(potValue, 0, 1023, 0, 180); // Map to 0-180
myServo.write(angle);
Serial.print("Pot: ");
Serial.print(potValue);
Serial.print(" | Angle: ");
Serial.println(angle);
delay(15);
}
Wiring the potentiometer: Connect the outer pins to 5V and GND, and the middle (wiper) pin to A0.
Code Example 3: Controlling Multiple Servos
You can control up to 12 servos on most Arduino boards (Uno, Nano) using the Servo library. Each servo gets its own object and pin.
#include <Servo.h>
Servo panServo;
Servo tiltServo;
const int PAN_PIN = 9;
const int TILT_PIN = 10;
const int PAN_POT = A0;
const int TILT_POT = A1;
void setup() {
panServo.attach(PAN_PIN);
tiltServo.attach(TILT_PIN);
}
void loop() {
int panAngle = map(analogRead(PAN_POT), 0, 1023, 0, 180);
int tiltAngle = map(analogRead(TILT_POT), 0, 1023, 0, 180);
panServo.write(panAngle);
tiltServo.write(tiltAngle);
delay(15);
}
This creates a basic pan-tilt mechanism -- one potentiometer controls horizontal rotation, the other controls vertical tilt. Add a camera or ultrasonic sensor on top and you have a scanning platform.
Note: On the Arduino Uno, using the Servo library disables PWM output (analogWrite) on pins 9 and 10, regardless of which pins your servos are on. Plan your pin assignments accordingly.
Code Example 4: Smooth Movement with Easing
Jumping instantly to a target angle looks robotic and mechanical. Real-world applications need smooth acceleration and deceleration. This function implements ease-in-out movement:
#include <Servo.h>
Servo myServo;
void setup() {
myServo.attach(9);
myServo.write(0);
delay(500);
}
void loop() {
smoothMove(myServo, 0, 180, 2000); // 0 to 180 in 2 seconds
delay(500);
smoothMove(myServo, 180, 45, 1500); // 180 to 45 in 1.5 seconds
delay(500);
smoothMove(myServo, 45, 0, 1000); // 45 to 0 in 1 second
delay(500);
}
void smoothMove(Servo &servo, int startAngle, int endAngle, int duration) {
unsigned long startTime = millis();
int range = endAngle - startAngle;
while (true) {
unsigned long elapsed = millis() - startTime;
if (elapsed >= (unsigned long)duration) {
servo.write(endAngle);
break;
}
// Ease-in-out using smoothstep formula
float t = (float)elapsed / (float)duration; // 0.0 to 1.0
float eased = t * t * (3.0 - 2.0 * t); // Smoothstep
int currentAngle = startAngle + (int)(range * eased);
servo.write(currentAngle);
delay(10);
}
}
The smoothstep function t * t * (3 - 2t) produces an S-curve: the servo accelerates gently from the start, reaches peak speed in the middle, and decelerates smoothly into the target. This is the same easing function used in animation and game development.
You can swap in other easing curves:
- Ease-in only (slow start, fast end):
t * t - Ease-out only (fast start, slow end):
t * (2 - t) - Stronger ease-in-out:
t * t * t * (t * (6t - 15) + 10)(Perlin's smootherstep)
Common Issues and Fixes
Servo Jitter
Symptom: The servo vibrates or twitches at its position instead of holding still.
Causes and solutions:
- Noisy power supply -- Add a 100 uF electrolytic capacitor across the servo power rails (VCC to GND), as close to the servo as possible. This absorbs voltage spikes.
- Floating signal pin -- Ensure the signal wire has a solid connection. Loose jumper wires on breadboards are a common culprit.
- Mechanical load exceeding torque -- If the servo is straining to hold position, it will oscillate around the target. Reduce the load or use a stronger servo.
- Software: constant write calls -- If your loop writes the same angle every iteration, try calling
write()only when the angle actually changes.
Power Problems
Symptom: Servo moves weakly, Arduino resets randomly, or multiple servos behave unpredictably.
Root cause: Almost always insufficient current. A single MG90S can draw 700 mA under stall. Two servos can easily exceed what the Arduino voltage regulator provides.
Solution: Use a dedicated 5V 2A (or higher) power supply. A phone charger with exposed leads works for prototyping. For permanent builds, use a buck converter from a 7-12V supply.
Servo Not Reaching Full Range
Some servos do not reach exactly 0 or 180 degrees with the default pulse widths. Use attach() with custom microsecond values:
myServo.attach(9, 544, 2400); // Default range
myServo.attach(9, 500, 2500); // Extended range -- test carefully
Increase the range gradually and stop if the servo starts buzzing at the endpoints, which means you have exceeded its mechanical limits.
Using Servos with ESP32
The ESP32 does not support the Arduino Servo library directly because it uses a different PWM system. Instead, you use the LEDC (LED Control) peripheral or the ESP32Servo library, which wraps LEDC for you.
Using the ESP32Servo Library
Install ESP32Servo from the Arduino Library Manager, then use it almost identically to the standard Servo library:
#include <ESP32Servo.h>
Servo myServo;
const int SERVO_PIN = 13; // Any GPIO pin works on ESP32
void setup() {
myServo.setPeriodHertz(50); // Standard 50Hz servo
myServo.attach(SERVO_PIN, 544, 2400); // Pin, min us, max us
}
void loop() {
for (int angle = 0; angle <= 180; angle++) {
myServo.write(angle);
delay(15);
}
for (int angle = 180; angle >= 0; angle--) {
myServo.write(angle);
delay(15);
}
}
Using Raw LEDC (No Library)
If you want full control without an external library:
const int SERVO_PIN = 13;
const int LEDC_CHANNEL = 0;
const int LEDC_FREQ = 50; // 50 Hz
const int LEDC_RESOLUTION = 16; // 16-bit resolution (0-65535)
// At 50Hz, period = 20ms. 16-bit resolution = 65536 steps per 20ms.
// 1ms pulse = 65536 / 20 = 3277 ticks
// 2ms pulse = 6554 ticks
int angleToDuty(int angle) {
// Map 0-180 degrees to ~544us-2400us pulse width
int pulseUs = map(angle, 0, 180, 544, 2400);
return map(pulseUs, 0, 20000, 0, 65535);
}
void setup() {
ledcAttach(SERVO_PIN, LEDC_FREQ, LEDC_RESOLUTION);
}
void loop() {
for (int angle = 0; angle <= 180; angle += 5) {
ledcWrite(SERVO_PIN, angleToDuty(angle));
delay(50);
}
}
The ESP32 has 16 LEDC channels, so you can control up to 16 servos independently. This makes it excellent for multi-servo robotics projects, especially combined with its Wi-Fi and Bluetooth capabilities for wireless control.
ESP32 power note: The ESP32 3.3V pin cannot power servos. Always use an external 5V supply. The ESP32 GPIO outputs 3.3V logic, but most hobby servos accept 3.3V signals without issues.
Project Ideas
Once you are comfortable with the basics, here are practical builds to try:
1. Robotic Arm (3-4 Servos)
Use three MG90S servos for base rotation, shoulder, and elbow joints. Add a fourth micro servo for a gripper. Control with potentiometers first, then upgrade to serial commands or a smartphone app via Bluetooth.
2. Pan-Tilt Camera Mount (2 Servos)
Mount two servos in a pan-tilt bracket with a small camera module. Use joystick input for manual control or program automated scanning patterns. Pairs beautifully with an ESP32-CAM for a wireless security camera with motion tracking.
3. Automated Door Lock (1 Servo)
A single MG90S can throw a deadbolt latch. Trigger it with an RFID reader, keypad, or fingerprint sensor. The servo rotates 90 degrees to lock/unlock, and its holding torque keeps the bolt in place.
4. Automated Window Blinds (1-2 Servos)
Attach a servo to the tilt rod of window blinds. Program time-based schedules or connect a light sensor to automatically adjust throughout the day. An ESP32 adds Wi-Fi control so you can integrate with home automation systems.
5. Ultrasonic Radar Scanner (1 Servo + HC-SR04)
Mount an ultrasonic distance sensor on a servo. Sweep 0 to 180 degrees, taking distance readings at each angle. Send the data to Processing or a web interface to visualise a real-time radar sweep -- a classic and impressive project.
Power Supply Recommendations and Best Practices
Getting power right is the difference between a project that works reliably and one that glitches randomly.
Choosing a Power Supply
| Servos | Minimum Supply | Recommended |
|---|---|---|
| 1 micro servo (no load) | Arduino 5V pin | Works for testing only |
| 1 micro servo (loaded) | 5V 1A external | Phone charger or buck converter |
| 2-4 micro servos | 5V 2A external | Bench supply or 2A wall adapter |
| Standard servos (MG995/996R) | 6V 3A+ external | Bench supply or 4-cell NiMH pack |
| 6+ servos | 5-6V 5A+ external | Dedicated servo PSU or LiPo with BEC |
Wiring Best Practices
- Always connect grounds together. The Arduino GND, servo GND, and external PSU GND must all share a common ground.
- Add decoupling capacitors. Place a 100 uF to 470 uF electrolytic capacitor across the power rails near the servos. Add a 0.1 uF ceramic capacitor per servo for high-frequency noise filtering.
- Keep signal wires short. Long signal wires pick up noise. If you must run long cables, use shielded wire or add a 1k ohm resistor in series with the signal line.
- Use a power distribution board for projects with four or more servos. Screw terminals handle current better than breadboard contacts.
- Never hot-swap servos while the system is powered. The inrush current can cause brown-outs that reset your microcontroller.
- Detach idle servos. If a servo does not need to hold position, call
servo.detach()to stop sending pulses. This reduces current draw and eliminates idle jitter. Reattach when you need to move it again.
Wrapping Up
Servo motors are one of the most versatile actuators in your toolkit. With just three wires and a few lines of code, you can add precise mechanical movement to any project. Start with the basic sweep to verify your wiring, move to potentiometer control to understand the input-to-angle mapping, then graduate to smooth easing and multi-servo coordination.
The MG90S is the sweet spot for most projects -- small enough to fit anywhere, strong enough (with metal gears) to handle real loads, and affordable enough to use several in a single build. Pair it with an Arduino for simplicity or an ESP32 for wireless control, and you have the foundation for everything from robotic arms to home automation.
The most important takeaway: get the power supply right from the start. A dedicated 5V supply with proper grounding and decoupling capacitors will save you hours of debugging mysterious resets and jittery behaviour.
Now wire up a servo and make something move.



