Using Arduino to Control the Position and Speed of a Micro Servo Motor

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

When it comes to bringing small-scale robotics, animatronics, or automated camera rigs to life, few components pack as much punch per gram as the micro servo motor. These tiny actuators—typically weighing between 8 and 12 grams—are the unsung heroes of countless DIY projects, from robotic arms that mimic human fingers to pan-tilt camera mounts that track a face across a room.

But here’s the catch: most hobbyists only ever scratch the surface. They plug a servo into an Arduino, upload the standard “Sweep” example, and call it a day. What they miss is the rich, nuanced world of precise position control and variable speed ramping that a micro servo is actually capable of—if you know how to talk to it properly.

In this deep-dive guide, we’ll go far beyond the blinking-LED equivalent of servo control. We’ll dissect the internal anatomy of a micro servo, decode the 50 Hz PWM signal it craves, and then build a robust, non-blocking Arduino library-level control system that lets you command both angular position (0° to 180°) and angular velocity (degrees per second) with surgical precision. Whether you're building a six-legged walking bot or a laser turret that must not overshoot, this article will give you the firmware-level toolkit you need.


Why Micro Servos Behave Differently Than You Expect

Before we write a single line of code, we need to respect the hardware. A typical micro servo (like the SG90 or MG90S) is a closed-loop system enclosed in a tiny plastic or metal gearbox. Inside, you'll find:

  • A DC motor spinning at high RPM.
  • A potentiometer (variable resistor) attached to the output shaft, acting as a position feedback sensor.
  • A control IC that compares the pot’s voltage to the incoming PWM pulse width and drives the motor accordingly.

The 50 Hz Pulse Width Modulation (PWM) Secret

The standard control signal is a 50 Hz square wave (period = 20 ms). The width of the positive pulse dictates the target angle:

| Pulse Width (µs) | Approx. Angle | |------------------|---------------| | 500 – 600 µs | 0° (full counter-clockwise) | | 1450 – 1550 µs | 90° (neutral) | | 2400 – 2500 µs | 180° (full clockwise) |

Critical nuance: Not all micro servos are created equal. Some SG90 clones interpret 500 µs as -90° and others as 0°. Always calibrate your min/max pulse values using a manual writeMicroseconds() sweep before relying on absolute angles.

The Hidden Problem: Speed Is Not a Servo Spec

Here’s the myth that needs busting: you cannot directly command a micro servo’s speed via a single PWM pulse. The servo’s internal controller will always try to reach the target position as fast as its tiny motor allows (typically 0.1s per 60° for an SG90). So how do we control speed?

The answer lies in incremental setpoint steering. Instead of jumping the target angle from 0° to 180° in one step, we create a series of intermediate target angles, each separated by a small time delay. By controlling the step size and the delay between steps, we effectively control the average angular velocity.

But if we do this naively with delay(), we block the entire Arduino, making multi-servo or sensor-interrupt-driven projects impossible. We need a non-blocking, timer-based approach.


Building a Reusable Speed-and-Position Controller

Let’s architect a clean, object-oriented solution that we can drop into any sketch. We’ll use the built-in Servo.h library for the low-level PWM generation, but we’ll wrap it with our own logic for speed ramping and position queuing.

Hardware Setup for Testing

For this tutorial, you’ll need:

  • Arduino Uno (or any classic board with 16 MHz clock)
  • One micro servo (SG90 recommended for low current draw)
  • A 470 µF electrolytic capacitor across the servo’s power rails (to prevent brownouts)
  • External 5V power supply (do not power a micro servo directly from the Arduino’s 5V pin if you’re drawing high torque; use a separate BEC or USB power bank)

Wiring:

| Servo Wire | Arduino Pin | External Power | |------------|-------------|----------------| | Brown/Black (GND) | GND | GND (common) | | Red (VCC) | 5V (optional) | 5V+ (recommended) | | Orange/Yellow (Signal) | Pin 9 | — |


The Core Class: SmartServo

We’ll write a class that tracks three key variables:

  1. _currentAngle – the angle the servo is actually at (as far as we know).
  2. _targetAngle – where we want it to go.
  3. _speedDegPerSec – the desired maximum speed.

The magic happens in an update() method that we call as often as possible (e.g., inside loop() or from a timer interrupt). Inside update(), we compute the maximum allowed angular movement for the current time slice, then step _currentAngle toward _targetAngle by that amount.

The Math Behind Smooth Motion

Let’s say our loop runs every 10 ms (100 Hz). If we want the servo to move at 120°/sec, then per loop iteration, the servo can move at most:

maxDeltaPerTick = speedDegPerSec * (tickIntervalMs / 1000.0) = 120 * (10 / 1000.0) = 1.2 degrees

So each time update() is called, we move _currentAngle by ±1.2° (or less if it would overshoot _targetAngle). Then we call servo.write(_currentAngle).

Handling Non-Linearity and Overshoot

Micro servos are mechanical systems with inertia. If you send a step command of 180° at full speed, the internal PID controller will cause overshoot and ringing. Our incremental method eliminates this because the servo never sees a huge jump—it sees a smooth staircase of tiny movements. This also reduces mechanical wear on the plastic gears.

Full Code Implementation

cpp

include <Servo.h>

class SmartServo { private: Servo _servo; int _pin; float _currentAngle; float _targetAngle; float _speedDegPerSec; unsigned long _lastUpdateMs; int _minPulse; // microseconds for 0 deg int _maxPulse; // microseconds for 180 deg

public: SmartServo(int pin, int minPulse = 544, int maxPulse = 2400) { _pin = pin; _minPulse = minPulse; _maxPulse = maxPulse; _currentAngle = 90; // start at neutral _targetAngle = 90; _speedDegPerSec = 60; // default speed _lastUpdateMs = 0; }

void begin() {   _servo.attach(_pin, _minPulse, _maxPulse);   _servo.write(_currentAngle);   _lastUpdateMs = millis(); }  void setSpeed(float degPerSec) {   _speedDegPerSec = max(1.0, degPerSec); // avoid zero/negative }  void moveTo(float angle) {   _targetAngle = constrain(angle, 0, 180); }  void update() {   unsigned long now = millis();   unsigned long dtMs = now - _lastUpdateMs;   if (dtMs == 0) return; // avoid division by zero    // Convert time to seconds   float dtSec = dtMs / 1000.0;    // Max movement in this tick   float maxDelta = _speedDegPerSec * dtSec;    // Calculate difference to target   float error = _targetAngle - _currentAngle;    // Step toward target, but not more than maxDelta   if (abs(error) <= maxDelta) {     _currentAngle = _targetAngle; // arrived   } else {     _currentAngle += (error > 0) ? maxDelta : -maxDelta;   }    // Write to servo   _servo.write(_currentAngle);   _lastUpdateMs = now; }  float getCurrentAngle() { return _currentAngle; } bool isMoving() { return abs(_targetAngle - _currentAngle) > 0.1; } 

};

Putting It to Work: A Demo Sketch

Below is a complete demo that ramps the servo back and forth between 20° and 160° at two different speeds, while simultaneously reading a potentiometer to adjust the speed live. This shows non-blocking speed control.

cpp SmartServo myServo(9);

void setup() { Serial.begin(115200); myServo.begin(); myServo.setSpeed(90); // start at 90 deg/sec }

void loop() { // Read pot on A0 (0-1023) and map to speed 10-300 deg/sec int potVal = analogRead(A0); float speed = map(potVal, 0, 1023, 10, 300); myServo.setSpeed(speed);

// Non-blocking state machine for direction static bool goingUp = true; if (!myServo.isMoving()) { if (goingUp) { myServo.moveTo(160); } else { myServo.moveTo(20); } goingUp = !goingUp; }

// Critical: call update() as often as possible myServo.update();

// Print telemetry (optional) static unsigned long lastPrint = 0; if (millis() - lastPrint > 100) { Serial.print("Speed: "); Serial.print(speed); Serial.print(" deg/s | Current: "); Serial.println(myServo.getCurrentAngle()); lastPrint = millis(); } }


Advanced Speed Profiling: Acceleration and Deceleration

The above code gives you constant velocity control, but real-world systems often need trapezoidal motion profiles—accelerate at the start, cruise, then decelerate to a stop. Without this, a high-speed move will still cause mechanical shock at the endpoints.

Implementing a Simple Acceleration Ramp

We can modify our SmartServo class to include an acceleration limit. Instead of a fixed maxDelta per tick, we compute a current speed that ramps up and down.

Here’s the modified logic inside update():

cpp // Add member variables: float _currentSpeed = 0; float _accelDegPerSecSq = 500; // e.g., 500 deg/s^2

// Inside update(), replace the maxDelta calculation: float maxSpeedThisTick = _speedDegPerSec;

// If we're close to target, start decelerating float distToTarget = abs(targetAngle - _currentAngle); float neededStopDist = (currentSpeed * _currentSpeed) / (2 * _accelDegPerSecSq);

if (distToTarget < neededStopDist) { // Decelerate currentSpeed -= _accelDegPerSecSq * dtSec; _currentSpeed = max(0, _currentSpeed); } else { // Accelerate up to max speed _currentSpeed += _accelDegPerSecSq * dtSec; _currentSpeed = min(speedDegPerSec, _currentSpeed); }

float maxDelta = _currentSpeed * dtSec;

This ensures the servo eases into motion and glides to a stop—critical for camera gimbals or 3D printer pen plotters where overshoot ruins precision.


Calibration: Finding Your Servo’s True Pulse Limits

As mentioned earlier, cheap micro servos often have inconsistent pulse-to-angle mapping. To get true 0° and 180°, you must calibrate.

The Calibration Sketch

Run this sketch once, and use your serial monitor to send raw pulse widths.

cpp

include <Servo.h>

Servo s; void setup() { Serial.begin(9600); s.attach(9); Serial.println("Send pulse width (500-2500) to test."); } void loop() { if (Serial.available()) { int pulse = Serial.parseInt(); if (pulse >= 500 && pulse <= 2500) { s.writeMicroseconds(pulse); Serial.print("Set to: "); Serial.println(pulse); } } }

Procedure: 1. Send 600 – note the angle (should be near 0°). 2. Send 2400 – note the angle (should be near 180°). 3. If your servo hits its mechanical stop before reaching 180°, lower the max pulse to e.g., 2200. 4. Update the SmartServo constructor with these exact values.

Pro tip: For the SG90, common calibrated values are minPulse=500 and maxPulse=2500, but many clones work better with 544 and 2400 (the standard for Futaba servos). Always test.


Multi-Servo Synchronization: The Power of Non-Blocking Control

One of the biggest wins of our update() approach is that it scales effortlessly to multiple servos. Because we never use delay(), we can instantiate several SmartServo objects and call update() on each one in the same loop.

Example: Four-Legged Walker Sync

cpp SmartServo leg1(3); SmartServo leg2(5); SmartServo leg3(6); SmartServo leg4(9);

void setup() { leg1.begin(); leg1.setSpeed(150); leg2.begin(); leg2.setSpeed(150); leg3.begin(); leg3.setSpeed(150); leg4.begin(); leg4.setSpeed(150); }

void loop() { // Gait pattern: lift diagonal pairs static float phase = 0; phase += 0.02; // simple time step if (phase > 2 * PI) phase = 0;

// Generate sinusoidal positions for smooth gait float base = 90; float amp = 30; leg1.moveTo(base + amp * sin(phase)); leg2.moveTo(base + amp * sin(phase + PI)); // opposite phase leg3.moveTo(base + amp * sin(phase + PI)); leg4.moveTo(base + amp * sin(phase));

// Update all servos leg1.update(); leg2.update(); leg3.update(); leg4.update(); }

Because each servo’s speed is independently controlled, you can even make the robot’s gait slower on one side to turn—simply call setSpeed() differently for each leg.


Power Supply Pitfalls and Signal Integrity

Micro servos are notorious for causing Arduino resets when they stall. Here’s what you must know:

The Brownout Problem

When a micro servo starts or changes direction, it can draw 500 mA to 1 A spikes. If your Arduino is powered from USB (500 mA max), this spike will drop the voltage below 4.8V, triggering a brownout reset.

Solutions: - Always use a separate 5V 2A supply for the servo. - Connect all grounds (Arduino, servo supply, and any sensors) together. - Place a large capacitor (470 µF to 1000 µF) directly across the servo’s power terminals. This acts as a local energy reservoir for current spikes.

Signal Noise & Wire Length

If your servo is more than 20 cm from the Arduino, use a shielded cable or twist the signal wire tightly with the ground wire. Also, add a 100 Ω resistor in series with the signal line right at the Arduino pin to dampen ringing.


Going Beyond 180°: Continuous Rotation Servos vs. Micro Servos

It’s worth noting that there’s a common confusion between positional micro servos (our topic) and continuous rotation servos (often called "360° servos"). A continuous rotation servo ignores the absolute angle and instead uses pulse width to control speed and direction:

  • 1500 µs = stop
  • 1000 µs = full reverse
  • 2000 µs = full forward

Our SmartServo class is not for continuous rotation. If you need wheel control, you’re better off using a DC motor driver (like L298N) or a dedicated ESC. But if you need precise, repeatable angular positioning—like pointing a laser or adjusting a valve—our positional control method is exactly what you need.


Real-World Tuning: A Checklist for Butter-Smooth Motion

When you first run your servo with speed control, you might notice audible buzzing or jerky motion. Here’s a debugging checklist:

  1. Is the speed too high for the load? If your servo is moving a heavy arm, lower _accelDegPerSecSq to 200 or less.
  2. Is the loop time stable? If you have other blocking code (e.g., delay() for sensors), the update() interval will be irregular, causing stuttering. Use a timer interrupt or ensure the loop is as fast as possible.
  3. Are the pulse limits calibrated? If your min/max are off, the servo will hit mechanical stops and buzz loudly.
  4. Check the voltage under load. Use a multimeter on the servo’s VCC pin. If it dips below 4.5V during motion, your power supply is inadequate.

A Note on Servo Jitter at Low Speeds

Micro servos are analog devices; at very low speeds (below 15°/sec), you may see step-wise motion because the internal pot has limited resolution. To smooth this out, you can add a tiny bit of micro-interpolation—instead of writing the angle directly, write it in 0.5° increments every 2 ms. Our code already does this via the maxDelta logic, but if you find jitter, try reducing the update() interval to 5 ms and using _servo.writeMicroseconds() instead of _servo.write() for finer granularity.


Putting It All Together: A Target-Tracking Turret

Let’s finish with a practical project that combines everything: a pan-tilt camera turret that tracks a moving object (simulated by a joystick or an ultrasonic sensor). The key is that the pan servo uses speed control to avoid jarring the camera.

Wiring Additions

  • Pan servo on pin 9
  • Tilt servo on pin 10
  • Two potentiometers (or a joystick module) on A0 and A1

Sketch Highlights

cpp SmartServo pan(9, 544, 2400); SmartServo tilt(10, 544, 2400);

void setup() { pan.begin(); tilt.begin(); pan.setSpeed(80); // slow pan for smooth video tilt.setSpeed(60); // even slower tilt }

void loop() { // Read joystick (center ~512) int x = analogRead(A0) - 512; int y = analogRead(A1) - 512;

// Map joystick deflection to target angle offset // deadzone of +/- 20 to avoid drift if (abs(x) < 20) x = 0; if (abs(y) < 20) y = 0;

// New target = current + deflection * sensitivity float newPan = pan.getCurrentAngle() + x * 0.05; float newTilt = tilt.getCurrentAngle() + y * 0.05;

pan.moveTo(newPan); tilt.moveTo(newTilt);

pan.update(); tilt.update(); }

This creates a natural, inertia-like feel—the camera glides toward the target instead of snapping. Perfect for FPV gimbals or surveillance bots.


Final Thoughts on Firmware Mastery

Controlling a micro servo’s position and speed via Arduino is less about raw PWM generation and more about state machine design and time-sliced math. By decoupling the target from the actual position and using incremental steps, you gain the ability to simulate inertia, acceleration, and even complex multi-axis coordination—all without ever blocking the CPU.

Remember these three pillars:

  1. Always use non-blocking updates – your Arduino has better things to do than wait for a servo to arrive.
  2. Calibrate your pulse limits – 180° is a lie until you verify it.
  3. Respect the power budget – a micro servo is a hungry little motor in a fancy dress.

Now go grab an SG90, wire it up, and try ramping it from 0° to 180° at 10°/sec. Watch how it struggles and then smoothly glides. That’s not magic—that’s just good firmware. And with the SmartServo class in your toolbox, you’ll never go back to the crude delay()-based sweeps again.

Copyright Statement:

Author: Micro Servo Motor

Link: https://microservomotor.com/how-to-connect-a-micro-servo-motor-to-arduino/control-micro-servo-position-speed-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