ESP32 Servo Example: Control RC Servos Step-by-Step

What do you need before starting?
Running a servo on ESP32 needs only a few parts and a library install. Have these ready before writing a line of code.
Hardware
- ESP32 development board — the classic ESP32, S2, S3, and C3 all work with the ServoESP32 library
- A standard RC servo
- Jumper wires
Software
- Arduino IDE 1.8.11 or later — version 1.8.10 had a known build error with ServoESP32 that was fixed in 1.8.11
- ESP32 board support installed in your IDE
- The ServoESP32 library (covered in the next section)
If you prefer to work at the ESP-IDF level rather than the Arduino framework, the MCPWM peripheral is the native option. MCPWM is worth learning when you need complementary outputs with dead time, phase-synchronized channels, or fault braking — none of which a basic servo example requires. That path is covered at the end of this guide.
\2ESP32 connected with servo motors, rangefinder sensors, and alphanumeric display Image: Taqiya Chowdhury, CC BY-SA 4.0, via Wikimedia Commons
How do I install the ServoESP32 library?
The library is available in two package managers.
Arduino IDE
- Open the Library Manager: Sketch → Include Library → Manage Libraries.
- Search for
ServoESP32. - Click Install.
PlatformIO
ServoESP32 is listed in the PlatformIO library registry (library ID 1739). Find it in PIO Home's Libraries tab or add it directly to your platformio.ini.
Once installed, include it at the top of your sketch:
#include <Servo.h>
The library ships with two ready-to-run examples. 04-SimpleServoAngles demonstrates degree-based positioning, and 05-SimpleServoRadians shows radian control using the ServoFloat or ServoDouble class variants. Run one of the bundled examples first to confirm the hardware path works before adding your own logic.
How do I connect a servo to the ESP32?
The servo signal wire connects to a single GPIO on the ESP32. The ServoESP32 library's own frequency example uses GPIO 4:
const int servoPin = 4;
The ESP-IDF MCPWM documentation examples use GPIO 18:
.gen_gpio_num = 18
Both are examples only — any GPIO capable of outputting a PWM signal works as a servo signal pin. The specific pin is set in the attach() call or in the MCPWM generator configuration, so changing it only requires updating that one value.
For robotics projects that drive many servos simultaneously, some makers use a dedicated ESP32 servo board — an add-on that sits between the ESP32 and the servo array. That board typically receives the PWM signal from the ESP32 and handles the higher combined current draw of multiple servos independently.
What does the ESP32 servo example code look like?
The ServoESP32 attach() function signature is:
bool attach(
int pin,
int channel = CHANNEL_NOT_ATTACHED,
int minAngle = DEFAULT_MIN_ANGLE,
int maxAngle = DEFAULT_MAX_ANGLE,
int minPulseWidthUs = DEFAULT_MIN_PULSE_WIDTH_US,
int maxPulseWidthUs = DEFAULT_MAX_PULSE_WIDTH_US,
int frequency = DEFAULT_FREQUENCY
);
For a classic ESP32 board, calling attach(servoPin) with all defaults works. For ESP32-S2, S3, and C3, testing showed the default frequency is too low. Pass at least 200 Hz explicitly:
Servo servo1;
const int servoPin = 4;
const int frequency = 200; // Hz — required for S2/S3/C3
servo1.attach(
servoPin,
Servo::CHANNEL_NOT_ATTACHED,
Servo::DEFAULT_MIN_ANGLE,
Servo::DEFAULT_MAX_ANGLE,
Servo::DEFAULT_MIN_PULSE_WIDTH_US,
Servo::DEFAULT_MAX_PULSE_WIDTH_US,
frequency
);
The overall interface mirrors the standard Arduino Servo library, so existing Arduino servo code transfers with minimal changes. When you need radian-based angle control — common in robotics calculations — substitute ServoFloat or ServoDouble for Servo. The rest of the API stays the same.
How do I know if the servo is responding correctly?
After uploading, check these four things in order:
- Physical movement — the servo arm should rotate to the commanded position and hold it. If nothing moves, the signal is not reaching the servo.
- No constant jitter — jitter at rest almost always means a frequency mismatch. On S2/S3/C3 boards, confirm that 200 Hz was passed to
attach(). - attach() return value —
servo1.attach()returns abool. Printing it over Serial immediately tells you whether the library accepted the pin and channel assignment. - Run a bundled example first — loading
04-SimpleServoAnglesfrom the library's examples folder before testing your own sketch isolates whether a problem is in the hardware path or your code.
What are the most common mistakes with ESP32 servo code?
1. Wrong frequency on S2/S3/C3 boards
The most widely reported problem: servos on ESP32-S2, S3, and C3 do not move or jitter continuously. Testing confirmed the default frequency is too low for those chip variants. Set it to at least 200 Hz in the attach() call.
2. Building with Arduino IDE 1.8.10 Version 1.8.10 had a build error specific to ServoESP32. The fix shipped in 1.8.11. If your builds fail without an obvious code error, check the IDE version first.
3. Out-of-range MCPWM comparator values When using the MCPWM peripheral directly, a comparator value outside the timer's range never fires its event and the output freezes at its last level. Keep the compare value within the period.
4. Rebuilding generator actions at runtime instead of updating the comparator
In MCPWM, the right way to adjust the duty cycle while the timer is running is mcpwm_comparator_set_compare_value(). Rebuilding the generator action configuration is unnecessary overhead and the docs explicitly call this out as the runtime update pattern.
5. Reaching for advanced features before you need them
For a plain RC servo signal — high at timer zero, low at the compare match — a basic Servo object or the single-output MCPWM example is enough. Adding dead time, fault braking, or phase sync before a project requires them creates complexity with no benefit.
When should I use MCPWM instead of the ServoESP32 library?
For most servo tutorials and beginner projects, ServoESP32 is the right choice. The ESP-IDF MCPWM peripheral is the better option when a project grows beyond a plain servo signal.
| Need | ServoESP32 | MCPWM (ESP-IDF) |
|---|---|---|
| RC servo signal | ✓ | ✓ |
| Arduino framework | ✓ | — |
| Complementary outputs + dead time | — | ✓ |
| Phase synchronization across timers | — | ✓ |
| Fault / brake input | — | ✓ |
| Pulse-width measurement | — | ✓ |
The MCPWM design chains four objects: a timer supplies the time base, an operator owns waveform resources, a comparator sets the edge position, and a generator drives the GPIO. Setting resolution_hz = 1000000 in the timer configuration gives 1 MHz resolution, meaning each tick equals 1 µs. You then choose period_ticks to match the frequency you need for your application.
At run time, change the servo position by calling mcpwm_comparator_set_compare_value() — no need to recreate timers or generators. The ESP-IDF feature map lists the typical use case for a single-output MCPWM setup as 'servo, dimming, basic control', confirming it is a supported path even for simple projects.
Frequently asked questions
Does ServoESP32 work with PlatformIO?
Yes. It is listed in the PlatformIO library registry as ServoESP32 (ID 1739) and can be installed through PIO Home or added to platformio.ini.
What frequency must I use for ESP32-S2, S3, or C3 servo control?
At least 200 Hz, based on testing. Pass it explicitly as the last argument to attach() — the default frequency is insufficient on those chip variants.
Can I control a servo in radians instead of degrees?
Yes. Use ServoFloat or ServoDouble from the ServoESP32 library instead of Servo. The rest of the API is identical.
Which GPIO pins work for the servo signal?
The ServoESP32 frequency example uses GPIO 4; the ESP-IDF MCPWM documentation example uses GPIO 18. Any PWM-capable GPIO works — you set the pin in attach() or in the MCPWM generator configuration.
What is MCPWM and do I need it for a basic servo?
MCPWM is ESP32's Motor Control Pulse Width Modulator peripheral, accessible through ESP-IDF. For a basic RC servo signal it works fine, but its real value is in advanced cases: complementary outputs with dead time, phase synchronization, and fault braking that a simple servo tutorial does not need.
ServoESP32 is the fastest path to a working servo on ESP32 — install it from the Library Manager, pass 200 Hz to attach() on S2/S3/C3 boards, and you are done in minutes. For projects that need phase-aligned outputs, complementary drive, or hardware fault braking, the ESP-IDF MCPWM peripheral gives precise hardware-level control, and updating the comparator value at runtime is all it takes to reposition a servo without touching the rest of the waveform configuration.
Sources
github.comMotor Control Pulse Width Modulator (MCPWM) (esp-idf docs)github.comMCPWM Generator: Create the PWM Waveform (esp-idf docs)github.comRoboticsBrno/ServoESP32 (GitHub README)This guide summarises the public sources listed above in our own words. Check the original documentation before relying on exact figures.


