Using Arduino to Control the Position and Speed of a Micro Servo Motor
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:
_currentAngle– the angle the servo is actually at (as far as we know)._targetAngle– where we want it to go._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=500andmaxPulse=2500, but many clones work better with544and2400(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:
- Is the speed too high for the load? If your servo is moving a heavy arm, lower
_accelDegPerSecSqto 200 or less. - Is the loop time stable? If you have other blocking code (e.g.,
delay()for sensors), theupdate()interval will be irregular, causing stuttering. Use a timer interrupt or ensure the loop is as fast as possible. - Are the pulse limits calibrated? If your min/max are off, the servo will hit mechanical stops and buzz loudly.
- 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:
- Always use non-blocking updates – your Arduino has better things to do than wait for a servo to arrive.
- Calibrate your pulse limits – 180° is a lie until you verify it.
- 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
Source: Micro Servo Motor
The copyright of this article belongs to the author. Reproduction is not allowed without permission.
Recommended Blog
- How to Create a Simple Servo Sweep Program with Arduino
- How to Connect a Micro Servo Motor to Arduino MKR FOX 1200
- Creating a Servo-Controlled Automated Plant Watering System with Arduino
- Exploring the SG90 Micro Servo Motor: Features and Specifications
- Using Arduino to Control the Rotation Angle of a Micro Servo Motor
- How to Connect a Micro Servo Motor to Arduino MKR IoT Bundle
- How to Connect a Micro Servo Motor to Arduino MKR Zero
- Using Arduino to Control the Rotation Angle and Speed of a Micro Servo Motor
- Using Arduino to Control the Angle, Speed, and Direction of a Micro Servo Motor
- Creating a Servo-Controlled Automated Plant Watering System with Arduino
About Us
- Lucas Bennett
- Welcome to my blog!
Hot Blog
- Micro Servos with Metal vs Plastic Gears: Impacts on Drone Durability
- The Future of Micro Servo Motors in Smart Educational Systems
- Exploring the SG90 Micro Servo Motor: Features and Specifications
- Citizen Chiba Precision's Micro Servo Motors: Trusted by Professionals
- How to Implement Environmental Testing in Control Circuits
- How to Control Servo Motors Using Raspberry Pi and the RPi.GPIO Library for Beginners
- Using Arduino to Control the Rotation Angle of a Micro Servo Motor
- The Evolution of Micro Servo Motors: Top Brands Over the Years
- The Best Micro Servo Motors for Robotics: A Brand Comparison
- Micro Servo vs Standard Servo for RC Airplanes
Latest Blog
- The Impact of Motor Torque and Speed on System Load
- Using Arduino to Control the Position and Speed of a Micro Servo Motor
- How to Create a Simple Servo Sweep Program with Arduino
- Exploring Baumüller's Contribution to Micro Servo Motor Technology
- How to Choose the Right PCB Material for Your Project
- Building a Servo-Powered Automated Sorting Robot with Raspberry Pi and AI
- Using a Smartphone to Control Your Micro Servo Robotic Arm
- Choosing Correct Micro Servo Size for RC Boats with Hull Constraints
- Essential Tools and Materials for Building an RC Car
- The Role of Micro Servo Motors in Smart Healthcare Systems
- How to Control Servo Motors Using Raspberry Pi and the RPi.GPIO Library
- Diagnosing and Fixing RC Car ESC Throttle Response Issues
- Diagnosing and Fixing RC Car Motor Overload Issues
- How to Build a Remote-Controlled Car with GPS Navigation
- PWM in Audio Signal Processing: Techniques and Tools
- The Role of Micro Servo Motors in the Development of Smart Educational Tools
- Using Micro Servo Motors for Haptic Feedback in Robots
- Enhancing Precision in Robotics with Micro Servo Motors
- PWM in Power Electronics: Applications and Challenges
- Smart Window Film Covers: Pop-up Protection via Micro Servos