A hobby servo is the easiest actuator to control from an Arduino: three wires, one library, one line of code to set an angle. It’s also where most robotic arm projects start. This guide covers the wiring, the power supply that keeps it from misbehaving, and working code — from a simple sweep to knob and serial control.

How a servo is controlled

A hobby servo reads a stream of pulses, one roughly every 20 ms (50 per second). The width of each pulse sets the angle: around 1.5 ms is the centre, shorter pulses turn one way and longer ones the other. The servo’s internal circuit compares that target with a potentiometer on its output shaft and drives the motor until they match — the closed loop explained in how robotic arms work.

The Arduino Servo library generates those pulses for you. By default it maps write(0) to a 544 µs pulse and write(180) to 2400 µs, and it starts a newly attached servo at 1500 µs. Real servos vary, so you’ll often narrow that range — see calibrating the range below.

Wiring

Servo wireColour (typical)Connects to
GroundBrown or blackSupply ground and Arduino GND
Power (+)Red5–6V supply positive
SignalOrange, yellow or whiteAny Arduino digital pin

The rule that saves the most debugging time: the servo supply’s ground must be joined to the Arduino’s ground. The signal is measured relative to ground; without a shared reference the servo twitches or ignores you.

Powering it properly

For a single SG90 with nothing attached, the Arduino’s 5V pin will do for a quick test. For anything more, give the servos their own supply:

  • A 5–6V supply sized for the stall current of every servo that can move at once — a 5V 10A adapter covers a typical arm.
  • A 1000 µF capacitor across the servo power rail to absorb the current spikes of servos starting together.
  • Thicker wire on the power rail than on the signal wires.

The full sizing maths is in powering a robotic arm, and the wiring diagram shows the complete circuit.

Example 1 — sweep

The first sketch every servo should run: it proves the wiring, power and code are all working before you bolt anything to the horn.

#include <Servo.h>

Servo servo;

void setup() {
  servo.attach(9);            // signal wire on pin 9
}

void loop() {
  for (int angle = 0; angle <= 180; angle++) {
    servo.write(angle);
    delay(15);                // ~15 ms per degree: a slow, safe sweep
  }
  for (int angle = 180; angle >= 0; angle--) {
    servo.write(angle);
    delay(15);
  }
}

If the servo buzzes or strains at either end, it’s being pushed past its mechanical stop — limit the loop to, say, 10–170 until you’ve calibrated it.

Example 2 — control with a potentiometer

A 10K potentiometer gives you a hand-driven servo — and one knob per joint is the simplest possible controller for a robotic arm.

#include <Servo.h>

Servo servo;
const int POT_PIN = A0;       // pot wiper; outer legs to 5V and GND

void setup() {
  servo.attach(9);
}

void loop() {
  int raw = analogRead(POT_PIN);              // 0–1023
  int angle = map(raw, 0, 1023, 0, 180);
  servo.write(angle);
  delay(15);
}

Example 3 — set the angle from the Serial Monitor

Type an angle into the Serial Monitor (with “Newline” selected) and the servo moves to it. The same pattern lets a Python script or a Raspberry Pi drive the servo — see robotic arm with Python.

#include <Servo.h>

Servo servo;

void setup() {
  Serial.begin(9600);
  servo.attach(9);
  Serial.println("Send an angle 0-180");
}

void loop() {
  if (Serial.available()) {
    int angle = Serial.parseInt();
    if (Serial.read() == '\n') {              // consume the newline
      angle = constrain(angle, 0, 180);
      servo.write(angle);
      Serial.print("Moved to ");
      Serial.println(angle);
    }
  }
}

Calibrating the pulse range

Because servos differ, write(0) and write(180) may not match your servo’s real end points. Find them with writeMicroseconds(): step the pulse down from 1500 until the servo stops moving or starts to strain, note the value, and repeat upward. Then attach with those limits so write() maps 0–180 onto the range your servo actually has:

servo.attach(9, 600, 2350);   // your measured min and max pulse, in µs

One more detail worth knowing: servo.read() returns the last angle you wrote, not the servo’s real position. A servo that’s stalled or overloaded still reports the commanded angle. If you need true position, add feedback — see encoders and position feedback.

More than one servo

Create one Servo object per servo and attach each to its own pin. The library handles up to 12 servos on an Uno (48 on a Mega), but while it’s running analogWrite() stops working on pins 9 and 10. For a full arm, a PCA9685 driver is the better route: it generates the pulses for up to 16 servos over two I2C wires and has its own servo power terminal. The Arduino robotic arm code guide has multi-joint, record-and-playback and smooth-motion sketches.

Troubleshooting

SymptomLikely causeFix
Servo jitters or twitchesMissing shared ground, or weak supplyJoin grounds; use a separate 5–6V supply
Arduino resets when the servo movesServo powered from the 5V pinMove servo power to its own supply
Buzzing at the end of travelDriven past the mechanical stopNarrow the angle or pulse range
Doesn’t move at allNot attached, wrong pin, or no powerCheck attach(), the pin number and servo voltage
analogWrite stopped workingServo library uses pins 9/10’s timerMove PWM outputs to other pins

Choosing the servo

An SG90 is ideal for learning and for light joints; a metal-gear MG996R is the usual choice for an arm’s shoulder and elbow. To work out what each joint of your own arm needs, use the robot arm torque calculator, and see servo motors for robotic arms for the servo classes. If you don’t have an Arduino yet, an Arduino starter kit with a servo included is the easiest way to start.

Frequently asked questions

How do I connect a servo motor to an Arduino?

A hobby servo has three wires. Brown or black is ground, red is power (+) and orange, yellow or white is the signal. Connect the signal wire to an Arduino digital pin, connect the servo's power and ground to a 5–6V supply, and join that supply's ground to the Arduino's GND. Then use the Servo library: attach() the pin and write() an angle.

Can the Arduino's 5V pin power a servo?

Only a single small servo with little or no load, and only for a quick test. Servos draw current spikes when they start and far more when loaded or stalled, which can pull the 5V rail down and reset the board. For anything real — several servos, or any servo lifting something — use a separate 5–6V supply and share the ground.

Which Arduino pins can control a servo?

Any digital pin. The Servo library generates the pulses itself using a hardware timer, so the pin doesn't need to be PWM-capable. On an Uno it can drive up to 12 servos, but while it's in use analogWrite() stops working on pins 9 and 10.

Why does my servo buzz or jitter?

Buzzing at the end of travel usually means the servo is being driven past its mechanical limit — narrow the angle or pulse range. Jitter while moving or holding is almost always power: an undersized supply, a missing shared ground, or servos powered from the Arduino's 5V pin.