Table of Contents
I’ve used Servo Motors in several projects and experiments in the DroneBot Workshop but have yet to publish a complete servo motor guide. That changes with this article (and its associated video).
This guide is designed to be a comprehensive resource for experimenters, makers, and electronics hobbyists working with small DC servo motors and microcontrollers. We’ll cover everything from basic operating principles to advanced control techniques and troubleshooting tips.
Introduction
Let me start by saying that working with servo motors can be fun! Having your creation come to life and move into the prescribed position can give you a sense of accomplishment, much more than getting a text formatting function to work in your latest program (at least it is for me).
Servo motors are known for their precise control of angular position. They enable you to animate projects, control mechanisms, and build robotic arms, remote-controlled vehicles, and almost anything your imagination can imagine.
This guide focuses on small DC servo motors (commonly used in RC models and microcontroller-based projects). It provides the theory, practical knowledge, and code you need to integrate them with popular microcontrollers like the Arduino Uno and the ESP32. We’ll also use the PCA9685 16-channel PWM driver for scenarios requiring multiple servo outputs.
Whether a beginner or an experienced maker, this article (and its accompanying video) will be your reference for working with small DC servo motors and microcontrollers.
DC Servo Motors
This guide focuses on small DC servo motors used by hobbyists, experimenters, and RC enthusiasts. These motors range from tiny Micro Servos to larger units capable of lifting over 50 kg.
We will cover both analog and digital servo motors, which look alike but operate differently internally.
Basic Servo Operating Principles
A servo motor is a self-contained electromechanical system that rotates an output shaft to a specific angular position in response to an electrical signal. Unlike regular DC motors that spin continuously, most servo motors are designed for precise angular positioning (Continuous Rotation Servos are the exception to this rule).
A servo motor is a closed-loop control system. It uses feedback to accurately achieve a desired position. Internally, a servo motor consists of a DC motor, a potentiometer (for position feedback), a control circuit, and gears. The control circuit compares the desired position (from the PWM control signal) with the current position (from the potentiometer). If there’s a difference, the motor is driven to correct the error until the desired position is reached.
The servo motor’s internal control circuit can be analog or digital. Both respond to the same PWM (Pulse Width Modulation) control signal, the pulse width of which determines the servo position.
Servo Motor Construction
A typical hobby servo motor contains:
- DC motor
- Potentiometer (for position feedback)
- Control circuit PCB
- Gear train (for torque multiplication)
- Output shaft
- Case (housing)
- 3-wire connection (ground, power, signal)
The potentiometer is mechanically linked to the output shaft, providing position feedback to the control circuit. The circuit continuously compares the desired position (from the input signal) with the actual position (from the potentiometer) and adjusts the motor accordingly.
Some digital servos use an encoder instead of a potentiometer.
The servo gears can be either plastic or metal. Metal gears are preferable for most applications as they are more durable than their plastic counterparts. Many “plastic” gears are nylon compounds that can be durable and waterproof.
Most servo motors are permanently sealed in a plastic or aluminum housing. Some are waterproof, making them ideal for RC watercraft.
Hobby servo motors come in three size categories:
Micro (or Nano) Servo
- Typical Dimensions: ~23 × 12 × 24 mm (height × width × length)
- Approximate Weight: 8–12 g
Popular in small-scale RC planes, lightweight robotics, and tight spaces where minimal weight and size are critical.
Lower torque capacity compared to larger classes but sufficient for small or low-load projects.
A popular Micro Servo is the SG-90 (Amazon Link)
Another popular Micro Servo is the MG-90 (Amazon Link)
Standard Servo
- Typical Dimensions: ~40 × 20 × 36 mm (height × width × length)
- Approximate Weight: 35–50 g
The most common form factor for hobbyists and RC enthusiasts, used in cars, boats, planes, and general robotics.
Provides a balance between moderate torque, decent speed, and convenient size.
A popular and inexpensive digital standard servo is the MG995 (Amazon Link)
If you require more torque, a good choice is the DS3235 (180-degrees, Amazon Link) (270-degrees, Amazon Link)
Giant (or Large-Scale) Servo
- Typical Dimensions: ~60 × 30 × 57 mm (height × width × length)
- Approximate Weight: 90 g and up (can exceed 150 g for very large servos)
Designed for large-scale RC models (e.g., quarter-scale aircraft, big gas-powered vehicles) and heavy-duty robotics applications.
These motors offer significantly higher torque and may operate at higher voltages (e.g., 6–7.4 V or more).
A typical Giant servo motor is the DS51150 (Amazon Link)
Giant serves often feature metal gears and robust cases to handle large mechanical loads.
Controlling Servo Motors
Hobby servo motors are controlled using Pulse Width Modulation, or PWM. As its name implies, PWM is a method of conveying information by modulating the width of a pulse.
Servo motors typically require a repeating pulse signal operating at approximately 50 Hz (a pulse every 20 milliseconds). The width of the high portion of the pulse within each 20 ms period determines the servo’s target angle.
In most servo motors, a pulse width between 0.5 and 1 millisecond corresponds to one extreme of the servo’s range, while a pulse width between 2 and 2.5 milliseconds corresponds to the other extreme. Any pulse width falling between these values will move the servo to an intermediate position.
For most servos, a 1.5 millisecond pulse width will center the shaft.
270-degree servo motors use the same PWM signals as their 180-degree counterparts.
A Continuous Rotation Servo motor uses the same range of PWM pulses to control both speed and direction.
A 1.5 millisecond pulse width will stop the motor. Increasing the pulse width will rotate the motor clockwise. The wider the pulse, the faster it will spin until it reaches maximum speed with a 2 to 2.5-millisecond pulse.
A pulse width of less than 1.5 milliseconds turns the motor counterclockwise. As the pulse width decreases, it spins faster until it reaches maximum speed at the motor’s minimum pulse width.
Analog vs. Digital Servo Motors
Analog and digital hobby servo motors appear and function similarly. Both are controlled by identical PWM pulses, and they share the same motor and potentiometer layout, although some digital servo motors use encoders in place of potentiometers. The main difference between the two types is found in their control PCBs.
Analog Servo Motors
Analog servos are the traditional type of servo motors and remain widely used due to their affordability and simplicity. They are typically less expensive than digital servo motors but offer lower precision and accuracy.
The following diagram illustrates the operation of a typical analog servo motor:
The incoming PWM signal is processed with a pulse width to voltage converter to obtain a control voltage. This is fed to a comparator, which compares it with a voltage derived from the potentiometer position.
The comparator’s output will be either zero, positive, or negative. It will only be zero if its two input voltages are equal, meaning that the motor shaft is in the correct position. Otherwise, it will be positive or negative, the voltage level reflecting the difference between the pot position and the desired position.
This output is fed to an error amplifier and then to an H-Bridge motor driver, which powers the motor. As the input is PWM pulses, the output will also be pulsed at the 50Hz rate of the input PWM signal.
Digital Servo Motors
Digital servos use a microcontroller to process the PWM signal, allowing faster updates (up to 500 Hz or more). This results in smoother operation, higher torque, and better precision. However, they consume more power and are generally more expensive than analog servos.
One method of constructing a digital servo motor is illustrated here:
The key difference in a digital servo motor control circuit is that it is based on a microcontroller instead of an analog comparator. The PWM signal is fed directly to the microcontroller, where it is precisely measured. It is compared to the potentiometer position, an analog voltage that is sent to an analog-to-digital converter (ADC).
The microcontroller’s output is a PWM signal fed to an H-Bridge motor driver to power the motor. It is a higher-frequency PWM signal, so it gives the motor more effective power. However, it can also cause a “whining” noise in the motor’s coils, which some users may find objectionable.
Here is a second method of building a digital servo motor. This design replaces the potentiometer with a digital encoder, either optical or hall-effect.
Servo Motor Specifications
One of the first things you should do when designing a project based on servo motors is to obtain the specification sheet(s) for the motor(s) you are considering using. They are a wealth of information.
Here are some specifications you should be looking at:
Torque
Servo motor torque measures the rotational force the motor can apply to its output shaft.
Torque is typically represented in kg·cm or oz·in; torque describes the servo’s ability to move or hold a load. For example, a servo rated at “2 kg·cm” can exert a force of 2 kilograms on a 1 cm lever arm.
When selecting a servo, it’s crucial to consider the required torque for your application, taking into account the weight of the load, the leverage involved in the mechanism, and any potential external forces the servo might need to overcome.
Insufficient torque will result in the servo being unable to reach the desired position or maintain it under load, potentially leading to damage or unreliable operation. Overloading a servo motor beyond its torque capacity can lead to overheating, reduced efficiency, or mechanical failure.
Voltage Range
The voltage range specification shows the acceptable input voltage for the servo’s internal electronics and motor.
This range is typically provided as a minimum and maximum voltage value (e.g., 4.8V – 6.0V). Supplying a voltage outside this range can lead to unpredictable behavior, reduced performance, or even permanent damage to the servo’s internal electronics and motor.
Standard hobby servo motors operate between 4.8 V and 6.0 V, but many modern designs can safely accept up to 7.4 V or more. Higher-voltage-capable servos often provide increased torque and speed but also require a robust power supply capable of delivering sufficient current at that voltage.
Rated Current
Rated current, sometimes specified as “no-load” current and “stall” current, describes how much current the servo motor draws during operation. This value is usually expressed in amperes (A) or milliamperes (mA).
No-load current indicates the draw when the servo runs without a load, while stall current is significantly higher and represents the current draw when the servo’s shaft is prevented from moving.
When designing power systems for multiple servos, you must ensure the power supply can handle the combined rated current of all servos.
Deadband
The dead zone, also known as deadband, is a small range of input signal change where the servo doesn’t respond. The servo’s motor stays inactive if the control pulse width falls within this narrow band around its last position, as the position is assumed to be “close enough.”
It’s essentially a zone of insensitivity designed to prevent the servo from constantly oscillating or “hunting” for the exact commanded position due to minor variations or noise in the control signal.
A smaller deadband width allows for finer control and higher accuracy, but it may also make the servo more sensitive to signal noise. Conversely, a larger deadband width reduces sensitivity to noise but may compromise positional accuracy. Some digital servos allow users to adjust the deadband width programmatically, enabling customization for specific applications.
The deadband is typically expressed in microseconds for PWM signals or as a percentage of the total control range.
Speed
Servo speed describes how quickly the output shaft can rotate, commonly specified as the time (in seconds) it takes to rotate 60° at a given voltage (e.g., 0.12 s/60° at 6.0 V). A lower speed value indicates a faster servo.
A servo’s speed is influenced by factors such as the motor’s internal gearing and the applied voltage. Higher voltages generally result in faster operation, up to the servo’s maximum rated voltage.
Operating Travel
Operating travel indicates the usable angular range of motion for the servo, typically around 0° to 180° for many hobby servos. Some specialized units can rotate 270°, and continuous-rotation servos turn indefinitely without a fixed stop.
Programmable digital servos allow you to adjust or limit their operating travel through software configuration.
Using Servo Motors with Microcontrollers
Servo motors can be driven using an RC receiver or a dedicated controller. However, as experimenters, we will use microcontrollers.
Microcontrollers are excellent for this application as they excel at precision timing and can produce beautiful, clean PWM waveforms. Many servo motor libraries are available for microcontrollers to simplify coding tasks.
Let’s see how we can use servo motors with some popular microcontrollers. We will start with a classic microcontroller, the Arduino Uno.
Servos with Arduino Uno
The Arduino Uno is an 8-bit microcontroller board based on the ATmega328P. It has 14 digital input/output pins, of which six can be used as PWM outputs. These outputs can all be used to drive a servo motor.
The I/O pins on the Uno are 5-volt logic, which is great for driving servo motors.
The Uno also has a 5-volt output for powering external sensors and displays. You should NEVER use this to power a servo motor; servo motors should ALWAYS be powered by their own power supply.
Arduino Servo Library
The Arduino Servo Library is a built-in library that makes it easy to control hobby servos from most Arduino boards. The library uses the Arduino’s timers to produce a stable 50 Hz output signal.
By default, the library works with servos that expect PWM pulse widths of 1ms to 2ms. However, you can also directly specify the PWM pulse width for servo motors with an extended range.
The primary functions of the servo library are as follows:
attach(pin)
- Associates a servo object with a specific Arduino pin for output.
- Example: myServo.attach(9);
attach(pin, minPulse, maxPulse)
- Same as attach(pin) but also allows specifying custom pulse width bounds (in microseconds).
- Example: myServo.attach(9, 600, 2400);
detach()
- Stops sending control signals to the servo, freeing the pin and allowing the servo to move freely or reduce power draw.
- Example: myServo.detach();
write(angle)
- Writes an angle to the servo (0–180 degrees); the library converts this to an appropriate pulse width (usually ~1 ms to ~2 ms).
- Example: myServo.write(90); // Move servo to 90°
writeMicroseconds(us)
- Directly specify the pulse width in microseconds, allowing fine-grained or non-standard positioning.
- Example: myServo.writeMicroseconds(1500); // ~center position
read()
- Returns the last written angle (0–180) sent by write().
- Example: int currentAngle = myServo.read();
readMicroseconds()
- Returns the last written pulse width in microseconds (from writeMicroseconds()).
- Example: int currentPulse = myServo.readMicroseconds();
attached()
- Returns true if a servo is currently attached to a pin, or false otherwise.
- Example: if (myServo.attached()) { … }
Arduino Servo Motor Hookup
The Arduino Uno has 6 PWM-capable I/O pins. Most Uno boards identify them with a “waveform” or “tilde” (~) mark. Any of these pins can be used to drive a servo motor; we will be using pin 9.
Just about any hobby servo motor can be used in the experiment. Make sure you know the operating voltage and provide a suitable power supply. Six AA cells make a convenient servo motor power supply.
Ensure you don’t use the Arduino’s 5-volt output for the servo!
Remember to tie the servo power supply ground and Arduino ground together.
Arduino Test Code
The library is very easy to use, as the following code sample will illustrate.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/* Arduino Uno Servo Motor Demo arduino-servo.ino Sweeps servo motor 180 degrees in both directions Servo connected to pin D9 DroneBot Workshop 2025 https://dronebotworkshop.com */ // Include Arduino Servo Library #include <Servo.h> // Create servo object and set position Servo myServo; int pos = 0; // Define Servo pin #define SERVO_PIN 9 void setup() { // Attach the servo myServo.attach(SERVO_PIN); } void loop() { // Sweep from 0 to 180 degrees for (pos = 0; pos <= 180; pos += 1) { // Set position myServo.write(pos); // Wait for servo to reach position delay(15); } // Sweep from 180 to 0 degrees for (pos = 180; pos >= 0; pos -= 1) { // Set position myServo.write(pos); // Wait for servo to reach position delay(15); } } |
We begin by including the Arduino Servo Library.
Next, we create an object called myServo and define a variable to hold its position (in degrees). We also define the servo I/O pin, pin 9 (If you decide to move the servo to a different pin, you’ll need to change the entry here).
In setup, we attach the servo object to the servo pin. If we were using multiple servo motors, we would have a different object for each one. Remember, the servo requires a PWM-capable pin on the Arduino Uno.
The Loop illustrates how we use a servo write to set the servo’s position. The servo moves immediately to the position specified in degrees, from 0 to 180. In this sketch, we sweep it back and forth, with a slight delay between moves to allow the servo motor to catch up.
Run the sketch and observe the servo motor. You’ll want to put a horn on to make it easier to see it move.
Remember, the library expects a 180-degree servo motor with 1 to 2 milliseconds pulse widths. If you need to use a motor with different specifications, you can do it as follows:
- Use the long form of the attach function to specify pulse width parameters.
- Specify position by pulse width instead of degrees
Doing this lets you drive virtually any servo motor using the Arduino Servo library.
Servos with ESP32
The ESP32 makes an excellent servo motor controller. Almost all of its 3.3-volt logic level outputs support PWM, and its high-precision timers generate very accurate waveforms.
You cannot, however, use the Arduino Servo Motor Library with an ESP32. The library relies upon Arduino-specific timers (actually ATMega328-specific) to operate, and it won’t work with the ESP32’s internal timers.
Fortunately, there are several servo motor libraries to choose from, including one that is almost a clone of the Arduino Servo Motor Library.
ESP32Servo Library
The ESP32Servo library is a popular and widely used library for controlling servo motors. It is a port of the Arduino Servo library optimized for the ESP32’s architecture. The library leverages the ESP32’s PWM timers to control up to 16 servos on individual channels, making it a versatile choice for servo control.
Here are the functions and commands included in this library:
Servo Class
- attach(pin) – Attaches servo to a pin
- attach(pin, min, max) – Attaches servo with custom min/max pulse widths
- write(angle) – Writes angle to the servo (0-180)
- writeMicroseconds(us) – Writes pulse width in microseconds
- read() – Returns current angle
- attached() – Returns true if the servo is attached
- detach() – Detaches servo from pin
ESP32PWM Class (For advanced control)
- allocateTimer(timer) – Allocates a timer for PWM generation
- attachPin(pin, freq, resolution) – Attaches a pin for PWM control
- writeScaled(duty, min, max) – Writes a scaled duty cycle
You’ll note that the basic ESPServo commands mimic commands for the Arduino Servo Library. This is intentional; this library is meant to allow the use of older Arduino code with an ESP32.
ESP32 Servo Motor Hookup
You can use almost any ESP32 module for this experiment. I used a Seeeduino XIAO ESP32-S3 module and hooked it to a servo motor, as illustrated in the following diagram:
Once again, please note that a suitable power supply will be required; I used four AA batteries to provide 6 volts.
Another thing worth mentioning is that pin D9 on the XIAO ESP32-S3 is actually ESP32 GPIO 8. You’ll need to note that when you write the code for the servo motor.
ESP32 Test Code
Here is some code that accomplishes the same objective as the Arduino code—move the motor shaft back and forth.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
/* ESP32 Servo Motor Demo esp32-servo.ino Uses ESP32Servo Library - https://github.com/madhephaestus/ESP32Servo Uses Seeeduino XIAO ESP32-S3 module Sweeps servo motor 180 degrees in both directions Servo connected to pin D9 DroneBot Workshop 2025 https://dronebotworkshop.com */ #include <ESP32Servo.h> // Create Servo object Servo myservo; // Set Servo pin (D9 on XIAO ESP32-S3 is GPIO 8) int servoPin = 8; void setup() { // Allow allocation of all timers ESP32PWM::allocateTimer(0); ESP32PWM::allocateTimer(1); ESP32PWM::allocateTimer(2); ESP32PWM::allocateTimer(3); // Standard 50 Hz servo myservo.setPeriodHertz(50); // Attaches to servo pin using min/max of 500us and 2400us myservo.attach(servoPin, 500, 2400); // Attaches to servo pin using min/max of 500us and 2400us // } void loop() { for (int pos = 0; pos <= 180; pos += 1) { myservo.write(pos); delay(15); } for (int pos = 180; pos >= 0; pos -= 1) { myservo.write(pos); delay(15); } } |
The code has many similarities to the Arduino code.
We start by including the ESP32Servo Library and creating an object, “myservo,” to represent the servo motor.
We also allocate the servo pin; as noted above, this is GPIO 8 on the ESP32.
In setup, we allocate all four of the ESP32’s PWM timers. We then set the PWM period (frequency) to 50 Hz and the minimum and maximum PWM pulse width. You can determine these values from your servo motor spec sheet.
The loop is quite similar to the Arduino sketch. We use two counters, incrementing and decrementing, to step the motor through a 0 to 180-degree range.
Load it up and watch it work! It will work identically to the Arduino Uno example.
Other ESP32 Servo Motor Libraries
The ESP32Servo Library can perform every task you will need for basic servo motor movements. But there are also other libraries that you can use, some with additional features and some that work in a different fashion.
ServoEasing Library
The ServoEasing library adds smooth ease-in and ease-out movement to servo control, creating smoother and more natural motion. It supports multiple easing functions such as linear, quadratic, cubic, sine, circular, and more.
ServoEasing Class
- attach(pin) – Attaches servo to a pin
- attach(pin, minPulseWidth, maxPulseWidth) – Attaches with custom pulse widths
- setEasingType(easingType) – Sets the easing type (LINEAR, QUADRATIC, CUBIC, etc.)
- setSpeed(degreesPerSecond) – Sets speed in degrees per second
- setSpeedForAllServos(degreesPerSecond) – Sets speed for all servos
- startEaseTo(endDegree) – Starts non-blocking movement with easing
- startEaseTo(endDegree, speed) – Starts movement with custom speed
- startEaseToD(endDegree, milliseconds) – Starts movement with fixed duration
- easeTo(endDegree) – Blocking movement with easing
- easeTo(endDegree, speed) – Blocking movement with custom speed
- easeToD(endDegree, milliseconds) – Blocking movement with a fixed duration
- setEasingTypeForAllServos(easingType) – Sets easing type for all servos
- update() – Updates non-blocking servo movements
- wait() – Waits for all servos to complete movement
- isMoving() – Returns true if the servo is moving
Easing Types
- EASE_LINEAR – Constant speed
- EASE_QUADRATIC_IN – Accelerating from zero velocity
- EASE_QUADRATIC_OUT – Decelerating to zero velocity
- EASE_QUADRATIC_IN_OUT – Acceleration until halfway, then deceleration
- EASE_CUBIC_IN, EASE_CUBIC_OUT, EASE_CUBIC_IN_OUT – Cubic easing
- EASE_QUARTIC_IN, EASE_QUARTIC_OUT, EASE_QUARTIC_IN_OUT – Quartic easing
- EASE_SINE_IN, EASE_SINE_OUT, EASE_SINE_IN_OUT – Sinusoidal easing
- EASE_CIRCULAR_IN, EASE_CIRCULAR_OUT, EASE_CIRCULAR_IN_OUT – Circular easing
- EASE_BOUNCE_IN, EASE_BOUNCE_OUT, EASE_BOUNCE_IN_OUT – Bouncing effect
The library can be installed directly from the Arduino IDE and comes with many example files. Most of the examples are for multiple servos, including this one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
/* * Simple.cpp * * Shows smooth linear movement from one servo position to another. * This example does not use interrupts and should therefore run on any platform where the Arduino Servo library is available. * * Copyright (C) 2019-2022 Armin Joachimsmeyer * armin.joachimsmeyer@gmail.com * * This file is part of ServoEasing https://github.com/ArminJo/ServoEasing. * * ServoEasing is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>. */ #include <Arduino.h> #include "ServoEasing.hpp" #include "PinDefinitionsAndMore.h" /* * Pin mapping table for different platforms - used by all examples * * Platform Servo1 Servo2 Servo3 Analog Core/Pin schema * ------------------------------------------------------------------------------- * (Mega)AVR + SAMD 9 10 11 A0 * 2560 46 45 44 A0 * ATtiny3217 20|PA3 0|PA4 1|PA5 2|PA6 MegaTinyCore * ESP8266 14|D5 12|D6 13|D7 0 * ESP32 5 18 19 A0 * BluePill PB7 PB8 PB9 PA0 * APOLLO3 11 12 13 A3 * RP2040 6|GPIO18 7|GPIO19 8|GPIO20 */ ServoEasing Servo1; void setup() { Serial.begin(115200); while (!Serial) ; // Wait for Serial to become available. Is optimized away for some cores. // Just to know which program is running on my Arduino Serial.println(F("START " __FILE__ " from " __DATE__ "\r\nUsing library version " VERSION_SERVO_EASING)); /******************************************************** * Attach servo to pin and set servo to start position. *******************************************************/ Serial.println(F("Attach servo at pin " STR(SERVO1_PIN))); Servo1.attach(SERVO1_PIN, 45); // Attach pin and go to initial position of 45 degree delay(500); // Wait for servo to reach start position. } void loop() { Serial.println(F("Move to 135 degree with 40 degree per second blocking")); Servo1.easeTo(135, 40); // Blocking call, runs on all platforms Serial.println(F("Move to 45 degree with 40 degree per second blocking")); Servo1.easeTo(45, 40); // Blocking call, runs on all platforms delay(1000); } |
ESP32_ISR_Servo Library
The ESP32_ISR_Servo library is different in that it uses interrupt timers to control servos, providing more reliable and precise control. This approach minimizes conflicts with other operations, making it suitable for complex projects with critical timing.
ESP32_ISR_Servo Library Functions
- ESP32_ISR_Servos.setupServo(pin, min, max) – Sets up a servo with pulse range
- ESP32_ISR_Servos.setPulseWidth(index, pulseWidth) – Sets pulse width
- ESP32_ISR_Servos.setPosition(index, position) – Sets position (0-180)
- ESP32_ISR_Servos.getPosition(index) – Gets current position
- ESP32_ISR_Servos.deleteServo(index) – Removes a servo
- ESP32_ISR_Servos.enableAll() – Enables all servos
- ESP32_ISR_Servos.disableAll() – Disables all servos
- ESP32_ISR_Servos.toggle(index) – Toggles a servo between enabled/disabled
- ESP32_ISR_Servos.enable(index) – Enables a specific servo
- ESP32_ISR_Servos.disable(index) – Disables a specific servo
The library can be installed directly from the Arduino IDE and comes with many examples, including this one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
/**************************************************************************************************************************** ESP32_ISR_MultiServos.ino For ESP32 boards Written by Khoi Hoang Built by Khoi Hoang https://github.com/khoih-prog/ESP32_ISR_Servo Licensed under MIT license The ESP32 has two timer groups, each one with two general purpose hardware timers. All the timers are based on 64 bits counters and 16 bit prescalers The timer counters can be configured to count up or down and support automatic reload and software reload They can also generate alarms when they reach a specific value, defined by the software. The value of the counter can be read by the software program. Now these new 16 ISR-based PWM servo contro uses only 1 hardware timer. The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers Therefore, their executions are not blocked by bad-behaving functions / tasks. This important feature is absolutely necessary for mission-critical tasks. Notes: Special design is necessary to share data between interrupt code and the rest of your program. Variables usually need to be "volatile" types. Volatile tells the compiler to avoid optimizations that assume variable can not spontaneously change. Because your function may change variables while your program is using them, the compiler needs this hint. But volatile alone is often not enough. When accessing shared variables, usually interrupts must be disabled. Even with volatile, if the interrupt changes a multi-byte variable between a sequence of instructions, it can be read incorrectly. If your data is multiple variables, such as an array and a count, usually interrupts need to be disabled or the entire sequence of your code which accesses the data. *****************************************************************************************************************************/ /**************************************************************************************************************************** This example will demonstrate the nearly perfect accuracy compared to software timers by printing the actual elapsed millisecs. Being ISR-based timers, their executions are not blocked by bad-behaving functions / tasks, such as connecting to WiFi, Internet and Blynk services. You can also have many (up to 16) timers to use. This non-being-blocked important feature is absolutely necessary for mission-critical tasks. You'll see blynkTimer is blocked while connecting to WiFi / Internet / Blynk, and elapsed time is very unaccurate In this super simple example, you don't see much different after Blynk is connected, because of no competing task is written From ESP32 Servo Example Using Arduino ESP32 Servo Library John K. Bennett March, 2017 Different servos require different pulse widths to vary servo angle, but the range is an approximately 500-2500 microsecond pulse every 20ms (50Hz). In general, hobbyist servos sweep 180 degrees, so the lowest number in the published range for a particular servo represents an angle of 0 degrees, the middle of the range represents 90 degrees, and the top of the range represents 180 degrees. So for example, if the range is 1000us to 2000us, 1000us would equal an angle of 0, 1500us would equal 90 degrees, and 2000us would equal 1800 degrees. Circuit: Servo motors have three wires: power, ground, and signal. The power wire is typically red, the ground wire is typically black or brown, and the signal wire is typically yellow, orange or white. Since the ESP32 can supply limited current at only 3.3V, and servos draw considerable power, we will connect servo power to the VBat pin of the ESP32 (located near the USB connector). THIS IS ONLY APPROPRIATE FOR SMALL SERVOS. We could also connect servo power to a separate external power source (as long as we connect all of the grounds (ESP32, servo, and external power). In this example, we just connect ESP32 ground to servo ground. The servo signal pins connect to any available GPIO pins on the ESP32 (in this example, we use pins 22, 19, 23, & 18). In this example, we assume four Tower Pro SG90 small servos. The published min and max for this servo are 500 and 2400, respectively. These values actually drive the servos a little past 0 and 180, so if you are particular, adjust the min and max values to match your needs. Experimentally, 550 and 2350 are pretty close to 0 and 180. *****************************************************************************************************************************/ #ifndef ESP32 #error This code is designed to run on ESP32 platform, not Arduino nor ESP8266! Please check your Tools->Board setting. #endif #define TIMER_INTERRUPT_DEBUG 1 #define ISR_SERVO_DEBUG 1 // Select different ESP32 timer number (0-3) to avoid conflict #define USE_ESP32_TIMER_NO 3 // To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error #include "ESP32_ISR_Servo.h" // Don't use PIN_D1 in core v2.0.0 and v2.0.1. Check https://github.com/espressif/arduino-esp32/issues/5868 // Don't use PIN_D2 with ESP32_C3 (crash) //See file .../hardware/espressif/esp32/variants/(esp32|doitESP32devkitV1)/pins_arduino.h #if !defined(LED_BUILTIN) #define LED_BUILTIN 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED #endif #define PIN_LED 2 // Pin D2 mapped to pin GPIO2/ADC12 of ESP32, control on-board LED #define PIN_D0 0 // Pin D0 mapped to pin GPIO0/BOOT/ADC11/TOUCH1 of ESP32 #define PIN_D1 1 // Pin D1 mapped to pin GPIO1/TX0 of ESP32 #define PIN_D2 2 // Pin D2 mapped to pin GPIO2/ADC12/TOUCH2 of ESP32 #define PIN_D3 3 // Pin D3 mapped to pin GPIO3/RX0 of ESP32 #define PIN_D4 4 // Pin D4 mapped to pin GPIO4/ADC10/TOUCH0 of ESP32 #define PIN_D5 5 // Pin D5 mapped to pin GPIO5/SPISS/VSPI_SS of ESP32 #define PIN_D6 6 // Pin D6 mapped to pin GPIO6/FLASH_SCK of ESP32 #define PIN_D7 7 // Pin D7 mapped to pin GPIO7/FLASH_D0 of ESP32 #define PIN_D8 8 // Pin D8 mapped to pin GPIO8/FLASH_D1 of ESP32 #define PIN_D9 9 // Pin D9 mapped to pin GPIO9/FLASH_D2 of ESP32 #define PIN_D10 10 // Pin D10 mapped to pin GPIO10/FLASH_D3 of ESP32 #define PIN_D11 11 // Pin D11 mapped to pin GPIO11/FLASH_CMD of ESP32 #define PIN_D12 12 // Pin D12 mapped to pin GPIO12/HSPI_MISO/ADC15/TOUCH5/TDI of ESP32 #define PIN_D13 13 // Pin D13 mapped to pin GPIO13/HSPI_MOSI/ADC14/TOUCH4/TCK of ESP32 #define PIN_D14 14 // Pin D14 mapped to pin GPIO14/HSPI_SCK/ADC16/TOUCH6/TMS of ESP32 #define PIN_D15 15 // Pin D15 mapped to pin GPIO15/HSPI_SS/ADC13/TOUCH3/TDO of ESP32 #define PIN_D16 16 // Pin D16 mapped to pin GPIO16/TX2 of ESP32 #define PIN_D17 17 // Pin D17 mapped to pin GPIO17/RX2 of ESP32 #define PIN_D18 18 // Pin D18 mapped to pin GPIO18/VSPI_SCK of ESP32 #define PIN_D19 19 // Pin D19 mapped to pin GPIO19/VSPI_MISO of ESP32 #define PIN_D21 21 // Pin D21 mapped to pin GPIO21/SDA of ESP32 #define PIN_D22 22 // Pin D22 mapped to pin GPIO22/SCL of ESP32 #define PIN_D23 23 // Pin D23 mapped to pin GPIO23/VSPI_MOSI of ESP32 #define PIN_D24 24 // Pin D24 mapped to pin GPIO24 of ESP32 #define PIN_D25 25 // Pin D25 mapped to pin GPIO25/ADC18/DAC1 of ESP32 #define PIN_D26 26 // Pin D26 mapped to pin GPIO26/ADC19/DAC2 of ESP32 #define PIN_D27 27 // Pin D27 mapped to pin GPIO27/ADC17/TOUCH7 of ESP32 #define PIN_D32 32 // Pin D32 mapped to pin GPIO32/ADC4/TOUCH9 of ESP32 #define PIN_D33 33 // Pin D33 mapped to pin GPIO33/ADC5/TOUCH8 of ESP32 #define PIN_D34 34 // Pin D34 mapped to pin GPIO34/ADC6 of ESP32 #define PIN_D35 35 // Pin D35 mapped to pin GPIO35/ADC7 of ESP32 #define PIN_D36 36 // Pin D36 mapped to pin GPIO36/ADC0/SVP of ESP32 #define PIN_D39 39 // Pin D39 mapped to pin GPIO39/ADC3/SVN of ESP32 #define PIN_RX0 3 // Pin RX0 mapped to pin GPIO3/RX0 of ESP32 #define PIN_TX0 1 // Pin TX0 mapped to pin GPIO1/TX0 of ESP32 #define PIN_SCL 22 // Pin SCL mapped to pin GPIO22/SCL of ESP32 #define PIN_SDA 21 // Pin SDA mapped to pin GPIO21/SDA of ESP32 // Published values for SG90 servos; adjust if needed #define MIN_MICROS 800 //544 #define MAX_MICROS 2450 int servoIndex1 = -1; int servoIndex2 = -1; void setup() { Serial.begin(115200); while (!Serial && millis() < 5000); delay(500); Serial.print(F("\nStarting ESP32_ISR_MultiServos on ")); Serial.println(ARDUINO_BOARD); Serial.println(ESP32_ISR_SERVO_VERSION); //Select ESP32 timer USE_ESP32_TIMER_NO ESP32_ISR_Servos.useTimer(USE_ESP32_TIMER_NO); servoIndex1 = ESP32_ISR_Servos.setupServo(PIN_D3, MIN_MICROS, MAX_MICROS); servoIndex2 = ESP32_ISR_Servos.setupServo(PIN_D4, MIN_MICROS, MAX_MICROS); if (servoIndex1 != -1) Serial.println(F("Setup Servo1 OK")); else Serial.println(F("Setup Servo1 failed")); if (servoIndex2 != -1) Serial.println(F("Setup Servo2 OK")); else Serial.println(F("Setup Servo2 failed")); } void loop() { int position; if ( ( servoIndex1 != -1) && ( servoIndex2 != -1) ) { for (position = 0; position <= 180; position++) { // goes from 0 degrees to 180 degrees // in steps of 1 degree if (position % 30 == 0) { Serial.print(F("Servo1 pos = ")); Serial.print(position); Serial.print(F(", Servo2 pos = ")); Serial.println(180 - position); } ESP32_ISR_Servos.setPosition(servoIndex1, position); ESP32_ISR_Servos.setPosition(servoIndex2, 180 - position); // waits 30ms for the servo to reach the position delay(30); } delay(5000); for (position = 180; position >= 0; position--) { // goes from 180 degrees to 0 degrees if (position % 30 == 0) { Serial.print(F("Servo1 pos = ")); Serial.print(position); Serial.print(F(", Servo2 pos = ")); Serial.println(180 - position); } ESP32_ISR_Servos.setPosition(servoIndex1, position); ESP32_ISR_Servos.setPosition(servoIndex2, 180 - position); // waits 30ms for the servo to reach the position delay(30); } delay(5000); } } |
ESP32ServoController Library
The ESP32ServoController library is an excellent solution for controlling multiple servos with high precision. It’s designed to overcome timing issues and performs better for applications requiring exact timing control. This high performance is achieved using the ESP32’s MCPWM peripheral for more accurate PWM signal generation.
Servo and ServoChannel Classes
- ServoChannel::attach(pin) – Attaches a channel to a pin
- ServoChannel::setAngle(angle) – Sets angle (0-180)
- ServoChannel::setPulseWidth(us) – Sets pulse width in microseconds
- ServoChannel::getPulseWidth() – Gets current pulse width
- ServoChannel::detach() – Detaches channel from pin
- Servo::attach(pin) – Attaches servo to pin
- Servo::detach() – Detaches servo
- Servo::attach(pin, minUs, maxUs) – Attaches with custom pulse range
- Servo::write(angle) – Sets angle (0-180)
- Servo::writeMicroseconds(us) – Writes pulse width in microseconds
- Servo::read() – Reads current angle
As with the previous libraries, several example files are included with this library. Thus one controls two servo motors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include <Arduino.h> #include <ESP32ServoController.h> using namespace MDO::ESP32ServoController; void setup() { Serial.begin(460800); delay(500); Serial.println(); Serial.println("Starting"); //configure our main settings in the ESP32 LEDC registry Esp32LedcRegistry::instance()->begin(LEDC_CONFIG_ESP32_S3); //change this for the relevant controller //!!in my case!! the servo needs specific timing parameters. Please check the relevant data sheet //Esp32LedcRegistry::instance()->setServoParams(510, 2490); //set the lower bound and upper bound respectivly to 500 usec and 2500 usec, with some 'safeguards' BestAvailableFactory oTimerChannelFactory; //used to select the best available timer & channel based on the hardware setup ServoFactoryDecorator oFactoryDecorator(oTimerChannelFactory); //let this ServoFactoryDecorator define the servo frequency to use and such //the above two are needed (variable scope related, in 'begin' only) ServoController oServo; const uint8_t uiPinNr = 16; if (!oServo.begin(oFactoryDecorator, uiPinNr)) { //3rd parameter is the default angle to start from: 90 degrees in this case Serial.println(" failed to init the servo.."); return; } delay(5000); Serial.println("Moving servo for demo:"); while (true) { Serial.println(" Moving servo to 0 degrees"); oServo.moveTo( 0.0, 5000, true); //move to 0 degrees in 5 seconds, and make this a blocking call Serial.println(" Done"); delay(2000); Serial.println(" Moving servo to 180 degrees"); oServo.moveTo(180.0, 10000, true); //move to 180 degrees in 10 seconds, and make this a blocking call Serial.println(" Done"); delay(2000); } } void loop() { } |
PCA9685 16-Channel PWM Driver
The PCA9685 is a 16-channel, 12-bit pulse-width modulation (PWM) driver commonly used for controlling multiple servos or LEDs. It communicates via the I2C protocol and can operate on 3.3 V or 5 V logic.
Each of its 16 output channels provides 4096 steps of PWM resolution, enabling fine control over servo positions or LED brightness. The PCA9685 is ideal for animating robotics projects or driving multiple RC servos from a single microcontroller.
PCA9685 Basics
It should be noted that the PCA9685 was not initially intended for use with servo motors. NXP developed the PCA9685 integrated circuit as a 16-channel LED controller, and some of its features are more suited to LEDs than servo motors.
Because of its obvious use for servo motors, Adafruit built a module around this board for use with servos. This essential means that the integrated circuit was mounted on a PCB with 16 servo connectors, connectors for I/O and power, and a connector for an independent servo motor power supply. As Adafruit made the module open source, several manufacturers have copied it. It has also been reworked as a Raspberry Pi HAT.
Using a PCA9685 has several advantages over using a microcontroller to drive a servo motor directly. As the PCA9685 has its own timing circuit, it doesn’t rely on the sometimes intermittent timing from a microcontroller, which can be affected if the microcontroller gets busy.
PCA9685 Module Pinout
Most PCA9685 modules follow the same basic design as the Adafruit module. This is illustrated in the diagram below:
The Logic & Power connections are duplicated on each edge of the board. This makes it easier to cascade these modules. You may connect up to 62 modules, each with its own I2C address. The Logic & Power connections are as follows:
- GND – Ground.
- OE – Output enable. When held LOW, the servo motor outputs are enabled. It is LOW by default, so it may be left unconnected.
- SCL – I2C Clock.
- SDA – I2C Data.
- VCC – Logic Power supply. This can be 3.3 or 5 volts.
- V+ – Motor power supply. Note that this connection does not have reverse polarity protection.
The bottom of the module has connectors for 16 servo motors (or LEDs).
On the top of the board is a terminal strip for connecting motor power. Using this strip adds the benefit of reverse voltage protection. Keep in mind that the power supply should be able to supply enough current to handle a situation where every servo motor has stalled.
To the right of the terminal strip are six solder pads. These can be used as jumpers to set the I2C address of the board. If left unused, the default I2C address is 0x40.
The PCA9685 module also has a filter capacitor for the motor power supply. This is not installed on some boards, allowing the user to select an appropriate value. The general rule of thumb for determining the capacitor value is 100 microfarads per motor.
I2C Addresses & Interfacing
The PCA9685 communicates via I2C and can work with 3.3 or 5-volt logic. No pullup resistors are included in the module; you can add them for long data runs.
The PCA9685 has a default I2C address of 0x40. You can change the address using the address selection pins (A0-A5). Each pin adds a binary value to the base address when connected to VCC (by shorting the jumper with solder). This lets you connect up to 62 devices to one I2C bus.
0x70 Address
There is also the “all-call address” of 0x70. This address will allow you to send commands to every PCA9685 on the I2C bus. This is used to reset all the devices simultaneously, although it is more applicable to LEDs than servo motors.
The “all-call address” must be enabled using the PCA9685 Mode Registers. The Adafruit library we will use with the PCA9685 can read and write these registers.
PCA9685 Timing
The PCA9685 uses a 12-bit resolution for PWM, meaning each time slice (or PWM cycle) is divided into 4096 steps. Each step corresponds to a specific point in time within the cycle, and the output can be either HIGH or LOW during each step. Here’s how it works:
Time Slice (PWM Period)
- The total duration of the PWM cycle is determined by the frequency set for the PCA9685. For example, at a frequency of 50 Hz (commonly used for servo motors), the period of one PWM cycle is 20 ms.
- This 20 ms period is divided into 4096 equal steps, each lasting approximately 4.88 microseconds (µs). These steps are referred to as Ticks.
ON and OFF Counters
- Each channel on the PCA9685 has two 12-bit registers: an ON counter and an OFF counter.
- The ON counter specifies the Tick number at which the signal changes from LOW to HIGH.
- The OFF counter specifies the Tick number at which the signal changes from HIGH to LOW.
PCA9685 Hookup
I’m using the Seeeduino XIAO ESP32-S3 board again, but you can use another ESP32 module if you wish. Actually, just about any microcontroller that supports I2C can be used.
I connected four small servo motors to the first four servo connectors (0 – 3). I also used a 6-volt power supply of four AA batteries to power the servo motors. Your computer powers the XIAO via the USB-C connector.
In addition to the I2C clock and data lines, I have wired the Output Enable (OE) pin to a GPIO on the ESP32. This is optional, but it can be used to disable all servo motors if required. Leave it disconnected if you prefer.
PCA9685 ESP32 Code
The Adafruit PWMServoDriver Library simplifies writing C++ code for the PCA9685 module. This library provides all the functions for configuring and using the PCA9685 for servo motors and LEDs. It includes some example sketches to get you started.
Here is a sketch that uses the Adafruit library to drive our four servo motors. You can easily expand it to drive up to 16 servo motors; just be sure your power supply can accommodate that many.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
/* PCA9685 Servo Demo pca9685-demo.ino Uses Adafruit Adafruit_PWMServoDriver Library Uses Seeeduino XIAO ESP32-S3 module Demonstrates operation of PCA9685 16-Channel 12-Bit PWM module DroneBot Workshop 2025 https://dronebotworkshop.com */ #include <Wire.h> #include <Adafruit_PWMServoDriver.h> // Initialize with default I2C address (0x40) Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); // Servo parameters (adjust based on your servos) #define SERVO_MIN 150 // Minimum pulse length count (out of 4096) #define SERVO_MAX 600 // Maximum pulse length count (out of 4096) void setup() { //Start PWM pwm.begin(); // Set PWM frequency to 50Hz (standard for servos) pwm.setPWMFreq(50); delay(10); } // Function to set angle (0-180) for a servo on specified channel void setServoAngle(uint8_t channel, int angle) { // Map angle to pulse length int pulse = map(angle, 0, 180, SERVO_MIN, SERVO_MAX); pwm.setPWM(channel, 0, pulse); } void loop() { // Demonstrate controlling four servos for (int i = 0; i < 4; i++) { // Sweep servo on channel 0 from 0 to 180 degrees for (int angle = 0; angle <= 180; angle += 5) { setServoAngle(i, angle); delay(50); } // Sweep back from 180 to 0 degrees for (int angle = 180; angle >= 0; angle -= 5) { setServoAngle(i, angle); delay(50); } } } |
We start by including the Wire library (for I2C) and the Adafruit PWMServoDriver libraries. After that, we initialize the PWMServo library using the default I2C address. If you change the address using the jumpers, you can specify it here.
We then set the minimum and maximum pulse widths in Ticks (not microseconds).
In the Setup, we start the PWM object we defined and set its frequency to 50 Hz.
Next, we define a function setServoAngle that accepts an angle and calculates the number of Ticks required to generate the correct pulse width. It is then passed to the set PWM command, with the starting Tick value of zero. We also pass through the channel numbers, from 0 to 15, to specify which motor we are controlling.
In the Loop, we simply step through the four servos. For each one, we do the same thing we have done in previous demonstrations: cycle the motors back and forth 180 degrees.
Load it up and test it out. You should see the four motors move in sequence.
You can adjust the number of motors by changing the “4” in the integer “i” in the first for loop.
Servo Motor Troubleshooting
Servo motors are fantastic devices that can resolve many design challenges but don’t always perform as you want. In this section, I’ll outline some common servo motor problems and provide advice for rectifying them.
Power Supply Issues
Servo motors can draw significant currents, especially when under load or multiple servos are used. Insufficient power can lead to jittering, erratic movement, or complete failure.
Common Problems
- Undersized or Unstable Power Supply – Servos often draw significant current, especially under load—much more than a typical onboard regulator can provide. If the power supply voltage sags or browns out, servos will jitter or reset.
- Voltage Mismatch – Many hobby servos are rated for ~4.8–6.0 V, while some high-torque digital servos can run at 7.4–8.4 V. Feeding the wrong voltage range can cause poor performance or permanent damage.
Solutions
- Use a Dedicated Servo Power Supply—Provide a separate 5–6 V (or higher if the servo supports it) regulated supply capable of supplying enough current for all servos. Never use a microcontroller’s 5 V pin to power a servo motor, even a small one.
- Ensure Common Ground – Always tie the servo power and microcontroller ground together. A missing or intermittent ground link is a frequent cause of random servo jitter.
- Check Capacity and Wiring – Use sufficiently thick wires for servo power lines, and consider distributing power along multiple points or using a bus bar if powering many servos.
- Add Filter Capacitors – Use capacitors across the servo power lines to stabilize voltage and reduce noise.
PWM Driver Issues
As servo motors are controlled using Pulse Width Modulation (PWM), any imperfection in the PWM signal can affect performance.
Common Problems
- Incorrect PWM Frequency – Most hobby servos expect about a 50 Hz signal. If the frequency is too high or too low, the servo may behave erratically or fail to hold position.
- Improper Pulse Width Range – Standard servos typically respond to pulse widths from ~1 ms (minimum) to ~2 ms (maximum). Sending pulses outside this range can cause mechanical stress or unpredictability.
- Software Timing or Library Conflicts – If other tasks or interrupts interfere with the PWM generation, you can get jitter or missed pulses.
Solutions
- Verify Frequency Settings – Double-check library or timer settings to confirm the PWM frequency is set to ~50 Hz for standard servos. Some digital servos can tolerate higher frequencies (e.g., up to 300 Hz), but verify specifications first.
- Use Established Libraries – Rather than manually writing PWM code, use reliable libraries such as Arduino’s “Servo.h,” the ESP32Servo library, or the Adafruit PCA9685 library. These libraries handle low-level timing details and help ensure stable signals.
- Scope or Frequency Counter – If possible, use an oscilloscope or frequency counter to confirm the pulse widths and frequency delivered to the servo.
Analog vs. Digital Servo Considerations
Sometimes, a digital servo motor makes sense instead of an analog one. In some situations, it’s the other way around.
Common Problems
- Analog Servo Jitter – Analog servos often exhibit noticeable jitter or humming, especially under low loads or near their center positions, if the supply voltage is slightly unsteady or if there’s electrical noise.
- Digital Servo Overload – Digital servos can draw significantly higher current (due to more aggressive internal PID loops), causing brownouts or resets in underpowered systems. They also produce more substantial holding torque, which is excellent for control but can strain a weak power setup.
- Wrong Servo Choice – Some builders use analog servos in demanding applications needing high torque and rapid response, only to find the servo cannot handle the load or speed. Conversely, using digital servos in a simple, low-power scenario may be unnecessarily expensive or cause unexpected power demands.
Solutions
- Switching Servo Types – If an analog servo is jittering due to small position changes, a digital servo’s tighter control loop may help at the cost of a higher current draw. Alternatively, an analog servo might suffice if you only need moderate torque and prefer minimal power draw at idle.
- Match Specs to Application – Check each servo’s torque, speed, and voltage requirements. Digital servos often benefit high-performance robotic arms, heavy RC vehicles, or 3D-printed mechanical linkages. Simple flaps, basic rotating mechanisms, or small hobby applications do well with analog servos.
- Tune or Program Digital Servos – Many digital servos are programmable, so you can adjust deadband, torque limiting, or response speed to combat issues like overcurrent or overshoot.
Interference
Common Problems
- Signal Noise on Control Line – Long servo leads can pick up electromagnetic interference (EMI), causing random twitches, especially if the wires run near high-current motors or wireless transmitters.
- Ground Loop Noise – Complex wiring or multiple return paths can introduce ground loops, adding noise to the servo signal.
Solutions
- Use Shielded Wires – If the servo cables are long, use shielded servo cables to minimize EMI pickup.
- Ferrite Beads or Chokes – Placing ferrite beads near the servo connector can filter high-frequency interference. These clip-on chokes are especially helpful in RC environments with high-power motors and speed controllers.
- Proper Routing – Keep servo signal wiring away from high-current lines or switching regulators. Route servo lines in a separate bundle to avoid inductive coupling.
- Other Motors – If using a brushed DC motor near the servo, add capacitors across the brushed motor terminals to reduce noise.
These four categories—power supply, PWM generation, servo type differences, and noise/interference—cover the most frequent root causes of DC servo motor malfunctions. By systematically addressing each area, you can significantly improve the reliability and performance of your servo-driven projects.
Conclusion
Servo motors are vital components in robotics and many hobby activities. They provide a method of adding controlled motion to your project. As we have seen, they are very easy to control using microcontrollers like Arduino and ESP32.
I hope you have enjoyed this rather long servo motor guide and that you find the code samples useful. The ZIP file you can download with the code also contains some MicroPython examples you can try with both a single servo motor and the PCA9685.
Parts List
Here are some components you might need to complete the experiments in this article. Please note that some of these links may be affiliate links, and the DroneBot Workshop may receive a commission on your purchases. This does not increase the cost to you and is a method of supporting this ad-free website.
Micro Servo SG90 – Amazon
Micro Servo MG90 – Amazon
Standard Servo MG995 – Amazon
Standard High Torque Servo (180-Degrees) DS3235 – Amazon
Standard Hi Torque Servo (270-Degrees) DS3235 – Amazon
Giant Servo DS51150 – Amazon
Adafruit PCA9685 – Adafruit
Resources
Code Samples – All the code used in this article in one easy-to-use ZIP file!
Article PDF – A PDF version of this article in a ZIP file.
Servo Splines – ServoCity Glossary for Servo Motors

