Electronics Software Development

Controlling A Servo Motor With An Arduino

Arduino Servo Graphic
Written by John Woolsey

Skill Level: Intermediate

Table Of Contents

Introduction

This tutorial will show you how to connect, configure, calibrate, and control a servo motor with an Arduino board. It will also demonstrate a variety of methods you can use to control a servo.

A basic understanding of electronics and programming is expected along with some familiarity with the Arduino platform. If you are new to Arduino, or would just like to refresh your knowledge, please see our Blink: Making An LED Blink On An Arduino Uno tutorial before proceeding with this one.

The resources created for this tutorial are available on GitHub for your reference.

What Is Needed

Background Information

The typical small servo motor, or simply servo, is an electromechanical device that translates electronic signals into distinct angular positions of 0-180 degrees on a motor’s shaft. It is constructed with a small DC motor, a gearing system that reduces the motor’s speed and increases torque, position control circuitry, and a potentiometer to provide position feedback. Servos are often used in radio-controlled vehicles and small robots for steering and other functions. The shaft of the servo motor is toothed so that items, such as gears, wheels, levers (horns), etc. can be attached to it.

Servos come in a variety of sizes. The larger the size, the more torque it produces, along with the associated extra power consumption. The standard sizes are generally categorized as micro, standard, and large, but some manufacturers provide additional sizes.

Three color coded wires are used to power and control a servo: power (red or brown), ground (black or brown), and signal (white, orange, or yellow). The signal wire is fed a pulse every 20 milliseconds with the width of that pulse, normally 1-2 ms, used to determine the position of the servo. Typically, 1 ms corresponds to the 0° position (farthest most counterclockwise), 2 ms for 180° (farthest most clockwise), and 1.5 ms for the center of rotation at 90°. However, these values can vary greatly by manufacturer and adjustments may need to be made with the control software or circuity to obtain better precision. Some servos only provide 90 degrees of rotation and others even allow for more than 180 degrees. There are even continuous rotation servos, but the pulse width for those is used to control their speed instead of position as is the case for standard servos.

I am using the TowerPro SG-5010 standard sized servo with the Arduino Uno WiFi Rev2 development board connected to a macOS based computer running the desktop Arduino IDE for this tutorial. If you are using a different servo, Arduino board, or computer setup, the vast majority of this tutorial should still apply, however, some minor changes may be necessary.

If you need assistance with your particular setup, post a question in the comments section below and I, or someone else, can try to help you.

Connecting The Servo

Before connecting the servo, or any circuitry, to your Arduino board, connect your board to your computer, open the Arduino IDE, upload the BareMinimum (Main Menu > File > Examples > 01.Basics > BareMinimum) sketch to the board, and then disconnect your board from your computer. This avoids accidental damage during wiring and power-up operations.

Attach one of the horns, or levers, that came with your servo to the motor shaft of the servo itself. This will help you visually see the position (angle) to which the servo is set.

WARNING:
Before connecting a servo to your Arduino, consult the specifications of the servo you plan to use to make sure you understand its operating limits and control parameters. If you try to drive the servo to a position outside of its range, it can stall and cause a current spike that may damage your servo or Arduino, or at the very least, cause your Arduino to reset. In addition, the current consumed by a servo increases with increasing loads and hits its maximum when the servo stalls due to too much load. For these reasons, I strongly recommend using an external power supply capable of providing 5-6 V DC @ 2A to power your servo until you understand its operation in your project. Once you understand the servo’s current needs (no pun intended), you can probably power a single small servo under light loads from the Arduino directly. Using large or multiple servos usually requires external power even during normal operation.

Recommended Hook Up

If you are using an external power supply, use some combination of power supply cables, jumper wires, alligator clip test leads, etc to

  • Connect the servo’s power wire to the power supply’s power terminal,
  • Connect the servo’s ground wire to both the power supply’s ground terminal and the GND pin of the Arduino, and
  • Connect the servo’s signal wire to GPIO pin D9 of the Arduino.

Alternate Hook Up

If you are powering the servo directly from the Arduino, use jumper wires to

  • Connect the servo’s power wire to the 5V pin of the Arduino,
  • Connect the servo’s ground wire to the GND pin of the Arduino, and
  • Connect the servo’s signal wire to GPIO pin D9 of the Arduino.

Once the servo is attached, connect your Arduino to your computer with the USB cable and power it up.

Basic Servo Operation

Now that our servo is connected, let’s see how we can make it move.

Open the Arduino IDE and create and save a new sketch named Servo_Basic with the code shown below.

#include <Servo.h>

uint8_t ServoA = 9;

Servo servoA;

void setup() {
   servoA.attach(ServoA, 1000, 2000);
}

void loop() {
   basicOperations();
}

void basicOperations() {
   servoA.write(90);
   delay(5000);
   servoA.write(0);
   delay(5000);
   servoA.write(90);
   delay(5000);
   servoA.write(180);
   delay(5000);
}

We will be utilizing the official Arduino Servo library in this tutorial. It is already included in the base IDE installation, so there is no need to install it separately. The above sketch covers the minimum configuration and operational statements needed to control a servo using the Servo library (included on the first line).

The Servo library expects the digital GPIO pins D9 and D10 to be used for servo connections and actually disables analogWrite() (PWM) functionality on those pins (except for the Mega board) while the library is in use. For this reason, D9 was selected for the connection to the servo’s signal wire as shown on line 3.

The instance of the servo (servoA) is then established on line 5 and attached (initialized) within the setup() function on line 8. The optional 1000 and 2000 arguments of the attach() method specify the pulse widths (in microseconds) of the 0° and 180° positions (angles) for the attached servo motor respectively. If they were not specified, the default values of 544 and 2400 would have been used. Since these values determine the endpoints of the servo’s range, I chose to be conservative in setting their values so as not to push the servo beyond its natural range. We will be adjusting these values later for the specific servo in use.

Since we will be covering multiple examples of functionality, I separated them into their own distinct functions. Therefore, the basicOperations() function, this first example, is the only function I am currently calling within the loop() function. This example function contains the most basic operations of telling the servo to move to its center point along with both endpoints with 5 second delays in between.

If there is something that needs further explanation, please let me know in the comment section and I will try to answer your question.

Verify your sketch to check for errors and then save your sketch when you are done editing.

Upload the sketch and watch the servo move to the specified positions. We now have a functional servo. You will probably notice that the 0° and 180° endpoints are a bit off, but we will fix that later. You may also notice that the servo moved in the opposite direction than you expected. Some servos move clockwise for smaller angles and some move in the opposite direction.

Setting Servo Positions Based On User Input

This next example enables us to input and set the servo angles (positions) via the Arduino IDE’s Serial Monitor.

Let’s start by adding the following DEBUG macro (after the #include statement) to enable the printing of debugging messages to the Serial Monitor that we will be incorporating into our code. You can set the macro to 0 to disable debugging printing if you so choose.

#define DEBUG 1

Next, add the following two lines to the beginning of the setup() routine to enable communication with the Serial Monitor. The first line initializes the serial connection and the second waits until a connection is established.

Serial.begin(9600);
while (!Serial);

Then add the following function to the end of the sketch.

void userInputOperations() {
   while (Serial.available()) {
      int angle = Serial.parseInt();
      if (angle < 0 || angle > 180) {
         Serial.print(F("ERROR: The angle value of "));
         Serial.print(angle);
         Serial.println(F(" is out of range."));
         return;
      }
      if (DEBUG) {
         Serial.print(F("Setting angle to "));
         Serial.print(angle);
         Serial.println(F(" degrees."));
      }
      servoA.write(angle);
   }
}

This routine begins by checking if serial data is available from the Serial Monitor. If so, we use the Serial library’s parseInt() method to retrieve an integer, the servo angle in this case, entered by the user via the Serial Monitor.

Lines 4-9 perform some error checking to ensure the angle received is within the operating range of the servo. If the angle is not in range, we print an error message to the Serial Monitor and exit the routine early.

If the DEBUG macro is set to 1, lines 10-14 print the angle we are about to set in the Serial Monitor.

Line 15 then instructs the servo to move to the specified angle.

Finally, replace the contents of the loop() function with the following to call the new userInputOperations() demonstration function instead of the original basicOperations() routine.

// basicOperations();
userInputOperations();

Verify and save your sketch when you are done editing.

Upload the sketch and open the Serial Monitor. Make sure the Serial Monitor‘s line ending setting (drop-down box located just to the right of the input field) is set to No Line Ending as it is required to properly parse the integer values entered within the input field.

Now, begin entering valid angle values, followed by the Enter key, into the input field of the Serial Monitor. You will see the angle that is about to be set within the Serial Monitor‘s output panel and then the servo will move to the specified angle. Again, you will probably see that the 0° and 180° endpoints are a bit off, but we will fix that in the next section.

Finally, try entering some invalid angles. You should see error messages being printed in the Serial Monitor.

Calibrating Servo Positions

In this section, we will take advantage of the previous section’s example userInputOperations() routine to finally fix the angles of the 0° and 180° endpoint positions.

WARNING:
The operations that we will be performing in this section could easily cause your servo to try to position itself outside of its natural operating range. This will usually cause the current spike we mentioned earlier that could damage your Arduino or servo. If you are not using an external power supply, you may want to skip this section, and instead, move ahead to the next section using the conservative 0° and 180° pulse width values we have been using so far.

Remember the extra arguments of 1000 and 2000 that we used with the Servo library’s attach() method? As a reminder, these correspond to the minimum pulse width (0°) and maximum pulse width (180°) endpoint settings allowed by the Servo library. We are about to adjust those in order to properly have the servo move to those positions with better accuracy while also giving us the full 0 to 180 degrees range.

Each servo will have a different full scale range that depends greatly on the manufacturer. This will be a trial-and-error process that could require quite a few attempts before settling on the right values.

Add the following macros just after the DEBUG macro to define our endpoint values. Notice that we are beginning with the conservative 1000 and 2000 microsecond values.

#define SERVO_A_0_DEGREES_PULSE_WIDTH 1000
#define SERVO_A_180_DEGREES_PULSE_WIDTH 2000

Then replace the original attach() method statement within the setup() routine

servoA.attach(ServoA, 1000, 2000);

with the new statement that utilizes those macros.

servoA.attach(ServoA, SERVO_A_0_DEGREES_PULSE_WIDTH, SERVO_A_180_DEGREES_PULSE_WIDTH);

Verify, save, and run your sketch and it should work exactly the same as it did before since we are still effectively using the same endpoint values.

Now let’s begin the iterative adjustment process. Every round involves performing each of the following steps.

  1. Slightly decrease the 0° position (from the initial 1000 value) and increase the 180° position (from the initial 2000 value) macro values.
  2. Rerun the sketch.
  3. Set the angle, from within the Serial Monitor input field, to the 90° midpoint position. This position should be parallel to the servo body.
  4. Set the angle to the 0° endpoint and take note of how close the new position is to the expected 0° endpoint (perpendicular to the servo body). If the servo stalls or does not move, go back to the last setting.
  5. Set the angle to the 180° endpoint and take note of how close the new position is to the expected 180° endpoint (perpendicular to the servo body). If the servo stalls or does not move, go back to the last setting.
  6. Depending on how close the last set and expected endpoints are relative to each other, choose the next endpoint values to try. For instance, if the endpoints are still far away from each other, try changing the value by 100. If they are very close, try a value change of 4.
  7. Stop the process once you reach the full 0-180 degrees range.

The final values obtained for the TowerPro SG-5010 servo I am using ended up being 500 and 2468 for the 0° and 180° endpoints respectively. As you can see, these values are quite different from the standard 1000 and 2000 conventions. These values are unique to the specific servo you are using. You should have a distinct set of macro definitions for each servo you have in your project.

Sweeping The Servo Through Given Angles

This final example demonstrates how to sweep through positions on your servo.

Add the following function to the end of your sketch.

void servoSweep(uint8_t startAngle, uint8_t stopAngle, uint8_t stepAngle = 1, unsigned long stepTime = 15) {
   if (startAngle > 180) {
      Serial.print(F("ERROR: The startAngle value of "));
      Serial.print(startAngle);
      Serial.println(F(" is out of range."));
      return;
   }
   if (stopAngle > 180) {
      Serial.print(F("ERROR: The stopAngle value of "));
      Serial.print(stopAngle);
      Serial.println(F(" is out of range."));
      return;
   }
   if (stepAngle < 1 || stepAngle > abs(stopAngle - startAngle)) {
      Serial.print(F("ERROR: The stepAngle value of "));
      Serial.print(stepAngle);
      Serial.println(F(" is out of range."));
      return;
   }
   if (DEBUG) {
      Serial.print(F("Sweeping angle from "));
      Serial.print(startAngle);
      Serial.print(F(" to "));
      Serial.print(stopAngle);
      Serial.print(F(" degrees in increments of "));
      Serial.print(stepAngle);
      Serial.print(F(" degree(s) with a "));
      Serial.print(stepTime);
      Serial.println(F(" ms step time."));
   }
   if (startAngle < stopAngle) {
      for (int angle = startAngle; angle <= stopAngle; angle += stepAngle) {
         if (DEBUG) {
            Serial.print(F("Setting angle to "));
            Serial.print(angle);
            Serial.println(F(" degrees."));
         }
         servoA.write(angle);
         delay(stepTime);
      }
   } else {
      for (int angle = startAngle; angle >= stopAngle; angle -= stepAngle) {
         if (DEBUG) {
            Serial.print(F("Setting angle to "));
            Serial.print(angle);
            Serial.println(F(" degrees."));
         }
         servoA.write(angle);
         delay(stepTime);
      }
   }
}

The servoSweep() function takes two regular arguments and two optional arguments and will step the servo through the angular positions based on the specified arguments.

  • startAngle is the value of the starting angle (position) with expected values of 0-180 degrees.
  • stopAngle is the value of the stopping angle (position) with expected values of 0-180 degrees.
  • stepAngle is the value of the optional stepping angle with expected values of 1-180 degrees. It defaults to 1 degree.
  • stepTime is the value of the optional stepping time. It defaults to 15 milliseconds.

The function begins by verifying the limits of the input arguments and notifying the user of any errors. It then proceeds to show the user the operation it is about to perform (beginning on line 20), if debugging is enabled, and then performs the actual stepping operation (beginning on line 31). The operation is split into two separate for-loops depending on whether the starting angle is smaller or larger than the ending angle. Each for-loop will step through the appropriate positions, print the angle that is about to be set (if debugging is enabled), move the servo to the new angle, and then delay for the appropriate amount of time.

Now, let’s utilize the above function within our sketch. Add the following sweepOperations() function to the end of your sketch that demonstrates how to use the previous servoSweep() sweeping function with a few example sweeps.

void sweepOperations() {
   servoSweep(0, 180);
   delay(5000);
   servoSweep(180, 0);
   delay(5000);
   servoSweep(45, 135, 15, 1000);
   delay(5000);
   servoSweep(180, 0, 45, 1000);
   delay(5000);
}

servoSweep(0, 180) – Sweeps the servo from 0° to 180° in 1° (default) increments with 15 ms (default) delays in between.

servoSweep(180, 0) – Sweeps the servo from 180° to 0° in 1° (default) increments with 15 ms (default) delays in between.

servoSweep(45, 135, 15, 1000) – Sweeps the servo from 45° to 135° in 15° increments with 1 second delays in between.

servoSweep(180, 0, 45, 1000) – Sweeps the servo from 180° to 0° in 45° increments with 1 second delays in between.

Finally, call the sweepOperations() routine from within the loop() function.

void loop() {
   // basicOperations();
   // userInputOperations();
   sweepOperations();
}

Verify, save, and upload your sketch and you should see the servo stepping through the various angles.

Before we end, now would be a good time to upload the BareMinimum (Main Menu > File > Examples > 01.Basics > BareMinimum) sketch in order to reset all of the board’s GPIO pins back to their default states. This ensures no outputs are being driven that may damage your board or connected electronics when plugging in your board for your next project.

Additional Resources

The following is a list of additional resources you may find helpful.

Summary

In this tutorial, we learned how to connect, configure, calibrate, and control a servo motor with an Arduino board.

Specifically, we learned

  • how servos generally work and some of the differences you may find among the servos produced by different manufacturers,
  • how to connect a servo to your Arduino board,
  • how to configure and set angular positions on a servo, and
  • how to calibrate the endpoints of the servo’s range.

I also provided some examples that demonstrate the different ways you can control your servo, including sweeping the servo through given angles and setting the servo’s position based on user input.

Hopefully, this tutorial provided you with a good understanding of how to incorporate servos into your own project.

The final source code used for this tutorial is available on GitHub. The GitHub version of the code is fully commented to include additional information, such as the program’s description, circuit connections, library references, code clarifications, and other details. The comments are also Doxygen compatible in case you want to generate the code documentation yourself.

Thank you for joining me on this journey and I hope you enjoyed the experience. Please feel free to share your thoughts or questions in the comments section below.

This tutorial is provided as a free service to our valued readers. Please help us continue this endeavor by considering a donation.

About the author

John Woolsey

John is an electrical engineer who loves science, math, and technology and teaching it to others even more.
 
He knew he wanted to work with electronics from an early age, building his first robot when he was in 8th grade. His first computer was a Timex/Sinclair 2068 followed by the Tandy 1000 TL (aka really old stuff).
 
He put himself through college (The University of Texas at Austin) by working at Motorola where he worked for many years afterward in the Semiconductor Products Sector in Research and Development.
 
John started developing mobile app software in 2010 for himself and for other companies. He has also taught programming to kids for summer school and enjoyed years of judging kids science projects at the Austin Energy Regional Science Festival.
 
Electronics, software, and teaching all culminate in his new venture to learn, make, and teach others via the Woolsey Workshop website.

2 Comments

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.