Driving a servo motor comes down to sending it a repeating pulse signal where the width of each pulse tells the motor where to point. The standard signal runs at 50 Hz (one pulse every 20 milliseconds), and you vary the pulse width between 1 ms and 2 ms to sweep the servo through its full range of motion. With the right wiring, a basic microcontroller, and a few lines of code, you can have a servo responding to your commands in minutes.
How the Control Signal Works
Servo motors use a technique called pulse width modulation, or PWM. Every 20 ms, your controller sends a brief electrical pulse. The length of that pulse determines the servo’s position. A 1 ms pulse moves the shaft to one extreme (around -90°), a 1.5 ms pulse centers it at 0°, and a 2 ms pulse sends it to the opposite extreme (+90°). Anything in between lands proportionally, so a 1.25 ms pulse would put the shaft roughly halfway between center and the minimum position.
The servo holds its position as long as the pulses keep arriving. Stop sending them and most servos will go limp, losing their holding force. This is why your code typically keeps the signal running continuously, even when the servo isn’t actively moving.
Continuous Rotation Servos
Not all servos work this way. Continuous rotation servos interpret the same pulse widths as speed and direction instead of position. A 1 ms pulse means full speed in one direction, 2 ms means full speed in the other, and 1.5 ms stops the motor entirely. If you’re building a small wheeled robot, these are the ones you want. If you need precise angular positioning (for a robotic arm or camera gimbal), you want a standard positional servo.
Wiring a Servo Motor
Hobby servos have three wires. The color coding varies by manufacturer, but the pattern is consistent: the darkest wire (black or brown) is ground, red is power, and the remaining wire (yellow, orange, or white) carries the control signal. If you’re ever unsure, remember that the drab color is always ground.
Here’s how the colors break down across common brands:
- Hitec: Black (ground), red (power), yellow or white (signal)
- JR: Brown (ground), brown (power), white (signal)
- Futaba: Black (ground), red (power), orange (signal)
Connect the ground wire to your microcontroller’s GND pin, the power wire to your voltage source, and the signal wire to whichever GPIO pin you plan to use for PWM output.
Powering the Servo Properly
This is where most beginners run into trouble. A typical hobby servo operates between 2.0 V and 5.0 V, and exceeding 5 V can damage the motor. The real issue isn’t voltage, though. It’s current. A servo draws very little at idle (under 10 mA in some models), but under load it can spike to 600 mA or more at stall. That kind of current draw will overwhelm a microcontroller’s onboard regulator, causing brownouts, resets, or jittery behavior.
The fix is straightforward: power the servo from an external source. Use a battery pack or a dedicated 5 V power supply rated for at least 1 A per servo. Connect the external supply’s ground wire to the microcontroller’s ground. This shared ground reference is essential. Without it, the control signal has no common baseline and the servo won’t respond reliably. Run the signal wire from your microcontroller’s GPIO pin directly to the servo, but let the external supply handle the heavy lifting on power and ground.
Voltage also affects performance in a measurable way. One common servo tested at 4.8 V moves 60° in 0.21 seconds and produces 3.3 kg/cm of torque. Bump that to 6 V (within its rated range) and it covers the same sweep in 0.16 seconds with 4.1 kg/cm of torque. If your project needs speed or holding strength, check your servo’s datasheet for its rated voltage range and power it toward the upper end.
Driving a Servo With Arduino
The Arduino platform makes servo control almost trivially easy. The built-in Servo library handles all the pulse timing for you. Here’s the basic structure:
Include the Servo library, create a Servo object, attach it to a pin, and call write() with an angle between 0 and 180. Behind the scenes, the library maps that angle to a pulse width. The default range in most Arduino cores is 540 to 2400 microseconds, which is slightly wider than the textbook 1000–2000 range to account for servos that can physically travel beyond 180°.
A minimal sketch looks like this: attach the servo to pin 9, write 0 to send it to one end, delay a second, write 90 to center it, delay again, and write 180 to send it to the other end. That’s enough to confirm your wiring works. From there, you can read a potentiometer with analogRead(), map the value to 0–180, and pass it to write() for real-time manual control.
If your servo doesn’t reach its full physical range, or overshoots and buzzes at the endpoints, you can override the default pulse limits using attach(pin, min, max), where min and max are in microseconds. Start with 1000 and 2000, then nudge the values until the servo moves smoothly across its full sweep without straining at either end.
Using 3.3 V Boards Like ESP32 or Raspberry Pi
Many modern microcontrollers (ESP32, Raspberry Pi Pico, STM32 boards) run their logic pins at 3.3 V instead of 5 V. Most hobby servos expect a 5 V signal. In practice, many servos will respond to 3.3 V logic just fine because they register anything above roughly 2 V as a “high” signal. But it’s not guaranteed, and you may see inconsistent behavior with some servo models.
If you run into problems, a logic level shifter solves the issue cleanly. A 74HCT series chip accepts inputs as low as 2 V and outputs a full 5 V signal. Alternatively, small MOSFET-based level shifter breakout boards work well and are widely available. This is a cheap insurance policy if you’re building something that needs to work reliably.
Analog vs. Digital Servos
You’ll see servos marketed as “analog” or “digital,” and the difference matters for certain applications. Both accept the same PWM signal from your controller, so the wiring and code are identical. The difference is internal.
An analog servo processes incoming pulses at the standard 50 Hz rate. A digital servo uses an onboard microprocessor to refresh its internal motor drive at roughly 300 Hz. That six-fold increase in refresh rate translates to faster response, stronger holding torque at rest, and smoother movement under load. The tradeoff is higher current draw, since the motor is being actively corrected much more frequently. For a simple sensor-triggered door latch, analog is fine. For a hexapod robot leg that needs to react quickly under varying loads, digital is worth the extra cost and power budget.
Fixing Jitter and Erratic Movement
Servo jitter (a constant twitching or buzzing even when the servo should be holding still) is one of the most common problems, and it almost always comes down to one of three causes.
The first and most frequent cause is insufficient power. If you’re powering the servo from the microcontroller’s 5 V pin, the voltage sags every time the servo moves, which destabilizes both the servo and the controller. Move the servo to an external power supply and the jitter often disappears immediately.
The second cause is noisy input. If you’re reading a potentiometer to control the servo’s position, small fluctuations in the analog reading translate directly into tiny position commands. The servo faithfully tries to follow each one, creating visible twitching. You can fix this by adding a small capacitor between the potentiometer’s output and ground to smooth the signal, or by averaging several readings in software before sending a command. Wiring the potentiometer directly to the microcontroller (rather than through a shared breadboard power rail) also helps keep servo motor noise from bleeding into the analog signal.
The third cause is electrical noise on the signal wire itself. Long wires act as antennas, picking up interference from motors, power supplies, or other electronics nearby. Keep signal wires short, route them away from power cables, and consider adding a small capacitor across the servo’s power and ground pins right at the connector to absorb voltage spikes.
If you’ve addressed all three and still see occasional twitching, try reducing how often your code updates the servo position. Sending a new command every 10–20 ms instead of as fast as possible gives the servo time to settle between moves.