Using Arduino to Control the Rotation Angle of a Micro Servo Motor

How to Connect a Micro Servo Motor to Arduino / Visits:9

Why Micro Servo Motors Are the Unsung Heroes of DIY Robotics

If you’ve ever built a robot arm, a pan-tilt camera gimbal, or even a tiny animatronic puppet, you already know the truth: the micro servo motor is the muscle behind the magic. These palm-sized powerhouses—typically the SG90, MG90S, or TowerPro variants—pack enough torque to move lightweight mechanisms with astonishing repeatability, all while drawing less than 500mA at stall. But here’s the kicker: unlike DC motors that spin endlessly, a micro servo gives you absolute positional feedback through a built-in potentiometer and a closed-loop control circuit. All you need to do is send a specific pulse width, and the servo snaps to a precise angle—usually 0° to 180°.

In this deep-dive guide, we’re going beyond the basics. We’ll explore not just how to wire up an Arduino to a micro servo, but how to think about pulse-width modulation (PWM), timing jitter, power stability, and even software-based smoothing. By the end, you’ll be able to command a micro servo with sub-degree accuracy, handle multiple servos without frying your board, and debug the classic “twitching servo” nightmare.


The Anatomy of a Micro Servo: What Makes It Tick

Before we touch a single jumper wire, let’s dissect the humble micro servo. Inside that plastic casing (usually 22mm x 11.5mm x 27mm for an SG90) you’ll find:

  • A DC gear motor – high-speed, low-torque, but geared down to deliver ~1.8 kg-cm at 4.8V.
  • A feedback potentiometer – attached to the output shaft, this variable resistor tells the control board the current angle.
  • A control board – compares the incoming signal pulse to the pot’s voltage and drives the motor forward or backward until they match.
  • A 3-wire pigtail – typically brown/black (ground), red (5V), and orange/yellow (signal).

The critical spec is the pulse width range. Most analog micro servos expect a 50Hz refresh rate (20ms period), with a 1ms pulse commanding 0°, 1.5ms commanding 90°, and 2ms commanding 180°. However, not all servos are created equal. Some MG90S variants might respond to 0.5ms–2.5ms for a wider 180° sweep, while high-voltage digital servos like the DS3218 can run at 333Hz with narrower pulses.

The “Dead Band” and Why It Matters

Every micro servo has a tiny dead band—a range of pulse width changes that produce no physical movement. For cheap SG90s, this might be ±5µs. That means if you send 1.500ms and then 1.502ms, the servo might ignore the change. This is why naive delay()-based sweeping looks jerky: you’re stepping outside the dead band in coarse jumps. To get smooth motion, you need to interpolate between angles in micro-steps that are larger than the dead band but smaller than what the eye perceives (about 1° per 10ms).


Hardware Setup: Wiring a Micro Servo Without the Smoke

Here’s the part where most beginners fry their Arduino. A micro servo on stall can draw up to 800mA. The Arduino Uno’s 5V regulator can only supply ~500mA from USB, and even less from a 9V battery. Never power a servo directly from the Arduino 5V pin. Instead:

  1. Use an external 5V 2A power supply (a phone charger or a UBEC) for the servo’s red and brown wires.
  2. Connect the Arduino GND to the external supply GND – this creates a common reference.
  3. Connect the servo signal wire to any PWM-capable pin – on a Uno, that’s pins 3, 5, 6, 9, 10, or 11.

The Classic “Brownout” Trap

If you see the Arduino resetting every time the servo moves, that’s a brownout. The servo pulls the shared 5V rail below 4.5V, and the ATmega328P’s brown-out detector trips. The fix is twofold: add a 1000µF electrolytic capacitor across the servo power pins (close to the servo), and keep the servo’s ground wire as short and thick as possible. For multi-servo projects, consider a separate servo shield with its own DC-DC buck converter.

Wiring Diagram (Mental Model)

[External 5V 2A] ---> Servo RED [External GND] ---> Servo BROWN ---> Arduino GND (common) [Arduino Pin 9] ---> Servo ORANGE (signal)

That’s it. No magic, no optocouplers needed for a single servo. For two or more, add a 470µF cap per servo.


Software Control: Beyond Servo.h – The Raw PWM Approach

The Arduino Servo library is great for beginners, but it has limitations: it uses Timer1 on the Uno, which disables analogWrite() on pins 9 and 10. Also, its default pulse range (544µs–2400µs) is not calibrated to your specific servo. To truly master angle control, you have two paths:

Path 1: The Servo Library (Good for 90% of Projects)

cpp

include <Servo.h>

Servo myServo; int angle = 0;

void setup() { myServo.attach(9, 500, 2500); // custom pulse range myServo.write(90); // center }

void loop() { for (angle = 0; angle <= 180; angle++) { myServo.write(angle); delay(15); // 15ms per degree = ~66°/sec } for (angle = 180; angle >= 0; angle--) { myServo.write(angle); delay(15); } }

This works, but the delay(15) is blocking. If you want to read a sensor while the servo moves, you need non-blocking timing.

Path 2: Direct Register-Level PWM (For the Perfectionist)

By writing directly to the ATmega328P’s Timer1 registers, you can generate a 50Hz signal with 1µs resolution. This gives you the ability to calibrate the exact pulse widths for your servo’s 0° and 180° positions. Here’s a minimal example:

cpp void setServoPulse(int micros) { // Assuming Timer1 is in phase-correct PWM mode, prescaler=8, TOP=39999 (50Hz) OCR1A = micros * 2; // because each tick = 0.5µs at 16MHz/8 }

void setup() { DDRB |= (1 << PB1); // Pin 9 as output TCCR1A = (1 << COM1A1) | (1 << WGM11); TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS11); ICR1 = 39999; // 20ms period }

void loop() { setServoPulse(1500); // 90° delay(1000); setServoPulse(1000); // 0° delay(1000); }

Why bother? Because you can now calibrate the min/max pulse widths by trial and error. For example, your SG90 might hit 0° at 540µs and 180° at 2410µs, not the textbook 1000µs–2000µs. Using the library’s default 544–2400 might leave you 2° short of full travel.


Calibrating Your Micro Servo: The 3-Point Method

No two micro servos are identical. The potentiometer tolerance alone can shift the zero point by ±5°. Here’s how to find your servo’s true range:

  1. Attach a long horn (arm) to the servo and mark a reference line.
  2. Send a 1.5ms pulse (or write(90)). Measure the angle with a protractor. If it’s not 90°, note the offset.
  3. Send 1.0ms and 2.0ms pulses. Measure the actual angles. You’ll likely see 0° and 180° only if you’re lucky.
  4. Adjust your pulse limits in code until you get exactly 0° and 180°. Store these as constants.

For the Servo library, use myServo.attach(pin, minPulse, maxPulse). For register-level, just adjust the micros values.

A Pro Tip: The “Soft Limits” Trick

If you’re using a servo with a plastic gearbox (like the SG90), slamming it into the end stops at full speed will eventually strip teeth. In software, always clamp your angle request to a safe range, e.g., constrain(angle, 5, 175). This leaves a 5° buffer on each side, drastically extending the servo’s lifespan.


Smooth Motion: The S-Curve and Easing Functions

Linear sweeping (write(angle) with equal delays) causes sudden acceleration and deceleration, which looks robotic and stresses the gears. For a professional feel, implement an easing function. The most popular is the S-curve (sigmoid):

cpp float easeInOutQuad(float t) { return t < 0.5 ? 2 * t * t : 1 - pow(-2 * t + 2, 2) / 2; }

void smoothMove(int targetAngle, int durationMs) { int startAngle = currentAngle; int steps = 50; // 50 intermediate positions for (int i = 1; i <= steps; i++) { float t = (float)i / steps; float eased = easeInOutQuad(t); int newAngle = startAngle + (targetAngle - startAngle) * eased; myServo.write(newAngle); delay(durationMs / steps); } currentAngle = targetAngle; }

This produces a motion profile where the servo accelerates gently, cruises, then decelerates—just like a real industrial robot. For even smoother results, increase steps to 100 and reduce the delay accordingly.

The “Jitter” Problem and How to Kill It

If your servo shivers at rest, it’s usually one of three things:

  • Power noise: Add a ferrite bead or a 100nF ceramic cap across the signal and ground.
  • Timer drift: The Servo library uses interrupts that can be delayed by other ISRs. Switch to register-level PWM.
  • Mechanical backlash: The gear train has play. Add a small rubber band load to preload the gears.

For a rock-solid hold, some servos support digital mode with a higher refresh rate (e.g., 300Hz). If your servo is digital, you can use the Servo library’s writeMicroseconds() at 50Hz, but digital servos actually prefer 200–333Hz. You’ll need to write a custom PWM generator for that.


Multi-Servo Coordination: The Power of a Servo Driver

Controlling 6 micro servos directly from an Arduino Uno is a recipe for timing headaches. Each Servo object uses an interrupt timer, and the Uno only has 3 timers. The practical limit is about 12 servos with the Servo library, but the CPU becomes a bottleneck. The clean solution is the PCA9685 16-channel PWM driver over I2C.

cpp

include <Wire.h>

include <Adafruit_PWMServoDriver.h>

AdafruitPWMServoDriver pwm = AdafruitPWMServoDriver();

define SERVOMIN 150 // ~0° (calibrated)

define SERVOMAX 600 // ~180° (calibrated)

void setup() { pwm.begin(); pwm.setPWMFreq(50); // analog servos }

void setServoAngle(uint8t channel, float angle) { uint16t pulse = map(angle, 0, 180, SERVOMIN, SERVOMAX); pwm.setPWM(channel, 0, pulse); }

With the PCA9685, the Arduino just sends I2C commands and can sleep. The driver board handles the 50Hz refresh for all 16 channels simultaneously. This is how you build a hexapod or a robotic hand without losing your sanity.

A Word on Servo Stalls

If you push a micro servo against a hard stop, it draws maximum current and heats up. The internal control board doesn’t have over-current protection. In software, always check if the servo has reached its target within a timeout. If not, cut power to that channel via a MOSFET or use a servo with stall detection (like the Feetech FS90R, though that’s continuous rotation).


Advanced Angle Feedback: Closed-Loop with an External Pot

Sometimes you need to know exactly where the servo is, not just where you commanded it. Micro servos don’t have a digital encoder, but you can hack it: attach a 10kΩ linear potentiometer to the output shaft (coaxially), and read its voltage with an Arduino analog pin.

cpp int readServoAngle() { int raw = analogRead(A0); // Map raw (0-1023) to 0-180 based on your pot's range return map(raw, potMin, potMax, 0, 180); }

Then, in your loop, you can implement a simple PID controller to correct for any missed steps. This is especially useful for robot arms that carry variable loads. The servo’s internal pot is not accessible externally, but an external pot glued to the shaft works fine for prototyping.

The “Stall Detection” Hack

By monitoring the current draw through a low-side sense resistor (0.1Ω) and an op-amp, you can detect when the servo is stalled (current spikes). This allows your Arduino to abort a motion and save the servo from burnout. It’s overkill for most hobby projects, but it’s a fun weekend build.


Real-World Example: A Pan-Tilt Camera Gimbal

Let’s tie everything together. You want a two-axis gimbal (pan on Y-axis, tilt on X-axis) using two MG90S micro servos. The tilt servo carries the camera, so it needs more torque. The pan servo only needs to swing the whole assembly.

Hardware: - Arduino Nano - 2x MG90S servos (or one SG90 for pan, one MG90S for tilt) - 5V 3A UBEC (battery eliminator circuit) - 1000µF capacitor across the power rails

Software: - Use the Servo library for simplicity. - Implement a smoothMove() function for both axes. - Read a joystick (analog) to set targets, with a dead zone to prevent drift.

cpp

include <Servo.h>

Servo panServo; Servo tiltServo;

int panAngle = 90; int tiltAngle = 90;

void setup() { panServo.attach(9, 500, 2500); tiltServo.attach(10, 500, 2500); panServo.write(panAngle); tiltServo.write(tiltAngle); }

void loop() { int x = analogRead(A0); // joystick X int y = analogRead(A1); // joystick Y

// Dead zone if (abs(x - 512) > 50) panAngle = constrain(panAngle + (x - 512) / 100, 0, 180); if (abs(y - 512) > 50) tiltAngle = constrain(tiltAngle + (y - 512) / 100, 10, 170);

// Smoothly move smoothMove(panServo, panAngle, 100); smoothMove(tiltServo, tiltAngle, 100); }

This is a robust base for a webcam tracker, a laser turret, or a solar panel alignment system. The key is the constrain() on the tilt axis to avoid flipping the camera.


Troubleshooting Common Micro Servo Failures

Even with perfect code, hardware can misbehave. Here’s a quick checklist:

  • Servo twitches but doesn’t rotate: Signal wire not connected, or ground missing. Check with an oscilloscope that you’re seeing a 50Hz square wave.
  • Servo rotates in one direction only: The pot is dirty or the control board is failing. Replace the servo.
  • Servo gets hot: It’s fighting a mechanical bind. Loosen the linkage or reduce the load.
  • Servo moves to wrong angles: The pulse range is miscalibrated. Re-run the calibration routine.
  • Arduino resets when servo moves: Power starvation. Add the big capacitor and use an external supply.

The “Humming” Servo Fix

If your servo hums at rest, it’s oscillating around the target. This is often because the dead band is too small for the mechanical load. Increase the dead band in your code by adding a tiny tolerance:

cpp if (abs(targetAngle - currentAngle) < 2) { // do nothing, servo is close enough }

Or, better yet, power down the servo via a MOSFET when idle. For battery-powered projects, this saves a surprising amount of energy.


Final Thoughts on Pushing Micro Servos to Their Limits

Micro servo motors are deceptively simple. A beginner can blink one with three lines of code, but a master can coax sub-degree precision, silent motion, and years of reliable service from a $3 part. The secrets are:

  1. Respect the power budget – always use a separate supply.
  2. Calibrate, don’t assume – every servo has its own pulse-to-angle curve.
  3. Smooth is strong – easing functions reduce gear wear.
  4. Monitor and protect – soft limits and stall detection save hardware.

Now go build something that spins, points, or waves. And when someone asks how you got that micro servo to move so gracefully, you can smile and say, “It’s all in the pulses.”

Copyright Statement:

Author: Micro Servo Motor

Link: https://microservomotor.com/how-to-connect-a-micro-servo-motor-to-arduino/control-micro-servo-angle-arduino.htm

Source: Micro Servo Motor

The copyright of this article belongs to the author. Reproduction is not allowed without permission.

About Us

Lucas Bennett avatar
Lucas Bennett
Welcome to my blog!

Tags