Servo Motor Tutorial for Arduino, ESP8266 and ESP32

Servo Motor Tutorial for Arduino, ESP8266 and ESP32

In this tutorial you learn everything you need to know about the functionalities of a servomotor.

First we learn the theoretical foundations how a servomotor works.

In multiple example you learn how to control one or multiple servo with your Arduino, ESP8266 or ESP32 microcontroller.

Servo Motors Thumbnail

Table of Contents

A servomotor is an electric motor which can precisely control the angle position, velocity and acceleration. Therefor the motor is using a closed-loop feedback mechanism. A Servo consists of the following parts:

  • An electric motor (DC motor or asynchronous motor or synchronous motor)
  • A sensor for position feedback
  • A controller to regulate the movement of the motor according to one or more adjustable setpoints, such as the desired angular position of the shaft or setpoint speed, in a control loop.

Servos are used in applications like robotics, CNC machines and different fields of automation technology.

Closed-loop Servomechanism

As mentioned at the beginning of this tutorial the servomotor uses a closed-loop feedback system to control the angle position, velocity and acceleration. The following picture shows the closed-loop.

servo motor position control diagram

Forward path:
The input for the driver is a pulse signal, which will be provided by our microcontroller in the following examples of this tutorial. The pulse signal define the position adjustment for the servo which results in a speed command and final in a current command which is the input for the inverter to turn the motor in the preferred direction.
There are different types of motors that can be used for a servo because the servomotor is not depended on the motor but on the functionality with the closed feedback loop. The most used motors are from brushed permanent magnet DC motors which are simple and cheap to AC induction motors for industrial use.

Backwards path:
The motor is paired with an encoder to provide speed and position feedback. The current feedback is provided directly by the inverter.
In out example we use a cheap servo motor which only uses the position feedback via a potentiometer. Therefore the motor always rotates at full speed. For industrial uses servomotors have a speed feedback loop as well through an optical rotary encoder. These types of servo are able to control the speed and therefore reach the desired position faster and precisely with less overshotting.
The position feedback to control the motion and the final position is compared to input signal. If there is a difference, an error signal is generated. The error signal causes the servo to rotate in the preferred direction until the error signal is zero. If the error signal is zero, the final position is reached and the motor stops rotating.

Input Pulse Signal

Now we want to control the servomotor with our Arduino or ESP based microcontroller. Therefore the pulse signal as input has to be created from the microcontroller as a square wave similar to a PWM signal.

Because we do not create a PWM signal, the digital pin for the motor does not have to be a PWM pin on your board. Every digital pin will work.

Most servo motors especially the cheaper ones which I use are not able to turn full 360 degrees but rather are able to turn between 0 and 180 degrees. With the PWM signal the control is 0 degrees for a pulse with of 1 ms, 90 degrees with a pulse with of 1.5 ms and 180 degrees with a 2 ms pulse with. The total cycle time is 20 ms where the PWM is repeated. The following picture shows the three different positions with their pulse with.

Creating Servo Examples

The following table gives you an overview of all components and parts that I used for this tutorial. If you want to support my work, you can buy something from the following links and I will earn a small commission. This does not affect the price you pay for the products.

If you are interested in components and parts that I used in other tutorials, visit the components and parts page.

 

Arduino Uno

Amazon

Banggood

AliExpress

OR

ESP8266 NodeMCU

Amazon

Banggood

AliExpress

OR

ESP32 NodeMCU

Amazon

Banggood

AliExpress

AND

Servo Motors

Amazon

Banggood

AliExpress

AND

Potentiometer

Amazon

Banggood

AliExpress

AND

PCA9685 16-Channel Servo Driver

Amazon

Banggood

AliExpress

We will use the Servo library in the following examples. If you do not know how to install a library in the Arduino IDE, here is a tutorial.

If we use the Servo library the PWM functionality on pin 9 and pin 10 of the Arduino boards are automatically disabled.

The servo I use is the Longruner SG90 Micro Servo Motor 9G RC (KY66-5). The following table shows the data sheet for the motor.

The stall torque is the torque the motor is pushing against a difference to the current position if you want to spin the motor with your hand. With a wide range of operating voltage the servo is able to work with Arduino boards (5V supply voltage) and EPS8266 boards (3.3V supply voltage).

The first example is the easiest one you can image. We want to turn the motor from 0 to 180 degrees in a step size of 1 and after pausing at 180 degrees for one second we want to turn the motor backwards to 0 degree but with a faster step size of 5.

#include "Servo.h"  // include Servo library
 
Servo myservo; //initialize a servo object
int angle = 0;    
 
void setup() 
{ 
  myservo.attach(9); // connect the servo at pin9
} 
  
void loop() 
{ // move from 0 to 180 degrees with a positive angle of 1
  for(angle = 0; angle < 180; angle += 1) { myservo.write(angle); delay(15); } delay(1000); // move from 180 to 0 degrees with a negative angle of 5 for(angle = 180; angle>=1; angle-=5)
  {                                
    myservo.write(angle);
    delay(5);                       
  } 

    delay(1000);
}

In the second example we want to use a potentiometer to set the angle of the servo motor. Therefor we read the value of the potentiometer which is between 0 and 1023, re-scale the value between 0 and 180 to move the motor between 0 and 180 degrees.

#include "Servo.h"  // include Servo library
 
Servo myservo;     //initialize a servo object
int potpin = 0;  // connect potentiometer to A0
int val;          // variable to read the value from the potentiometer
 
void setup() {
  myservo.attach(9);  // connect the servo at pin9
}
 
void loop() {
  val = analogRead(potpin);            // reads the value of the potentiometer (value between 0 and 1023)
  val = map(val, 0, 1023, 0, 180);     // scale it to use it with the servo (value between 0 and 180)
  myservo.write(val);                  // sets the servo position according to the scaled value
  delay(15);                           // waits for the servo to get there
}
Servo Arduino Poti

Connect Multiple Servo Motors

Of cause it is possible to connect multiple servos to your microcontroller and the Arduino Mega has a lot of pins. But there is a smarter way to connect multiple servos to your Arduino or NodeMCU using the I2C connection. If you want to know more about the I2C connection, you find a complete guide in this article.

To connect multiple servo motors over I2C we use the PCA9685 16-Channel Servo Driver which uses an on-board PWM controller to drive all 16 channels at the same time. Additionally you are able to connect multiple PCA9685 16-Channel Servo Drivers in a chain up to 62 drivers. Therefore we are able to control up to 992 servos with the 2 I2C pins.

PCA9685

The connection between the PCA9685 and the microcontroller is easy and shown in the following table as well as in the fritzing sketch.

If you do not know the SCL and SDA pins for your microcontroller, you find the pinouts for each board in the following articles: Arduino Nano, Arduino Uno, Arduino Mega, ESP8266, ESP32.

The 16 output ports (V+, GND and PWM) can be used to connect servos or LEDs. All outputs use the same PWM frequency which can be 1 kHz for LEDs and 60 Hz for servos. The maximum current per pin is 25mA. If you only use some servos like up to five you can power them from your microcontroller. But if you want to use more motors, I would recommend to power the servos with the 2-pin terminal and an external power supply. In the list of the parts you find some good choices for an external power supply.

The following sketches show how to connect the PCA9685 powered by the microcontroller or by an external power supply.

Servo Arduino PCA9685
Servo Arduino PCA9685
Servo Arduino PCA9685 external power supply
Servo Arduino PCA9685 external power supply
Servo NodeMCU PCA9685
Servo NodeMCU PCA9685
Servo NodeMCU PCA9685 external power supply
Servo NodeMCU PCA9685 external power supply

In the following example we want to use 3 servo motors at the same time. The script has no direct practical use but shows how to control multiple servos in one script over the I2C connection.

Parameterize the Servo Motor

First we have to calibrate the servo to the 0 degree and 180 degrees position. Therefor we use the sketch from the first example. Now you have to remember the exact position. Do not change the plastic attachment on the motor.

Now we have to find the optimal values for two parameters: MIN_PULSE_WIDTH and MAX_PULSE_WIDTH to define the position of the servo at 0 degree and 180 degrees. The following picture shows the desired result. I found that for the Longruner SG90 Micro Servo Motor 9G RC (KY66-5) the parameters are the following:

  • MIN_PULSE_WIDTH = 600
  • MAX_PULSE_WIDTH =2600
Find the Min and Max Pulse Width

Because these values are depending on you individual servos, we now have to find the optimal values for your servo motors.

We use a part of the future full sketch to let the motor settle at 0 degree. Use the following sketch and change the values for MIN_PULSE_WIDTH until the desired position is achieved. Maybe you will overshoot the position, then you have to tweak the parameter again. If you found the right value for MIN_PULSE_WIDTH we do the same with MAX_PULSE_WIDTH.

#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);

#define MIN_PULSE_WIDTH 600
#define MAX_PULSE_WIDTH 2600
#define FREQUENCY 50

void setup() 
{
  pwm.begin();
  pwm.setPWMFreq(FREQUENCY);
}

int pulseWidth(int angle)
{
  int pulse_wide, analog_value;
  pulse_wide = map(angle, 0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
  analog_value = int(float(pulse_wide) / 1000000 * FREQUENCY * 4096);
  return analog_value;
}

void loop() {
  pwm.setPWM(0, 0, pulseWidth(0));
  delay(1000);
}

Now we want to find the motor position for 180 degrees. We use the following sketch and change the value for MAX_PULSE_WIDTH until the 180 degrees position is found.

#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);

#define MIN_PULSE_WIDTH 600
#define MAX_PULSE_WIDTH 2600
#define FREQUENCY 50

void setup() 
{
  pwm.begin();
  pwm.setPWMFreq(FREQUENCY);
}

int pulseWidth(int angle)
{
  int pulse_wide, analog_value;
  pulse_wide = map(angle, 0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
  analog_value = int(float(pulse_wide) / 1000000 * FREQUENCY * 4096);
  return analog_value;
}

void loop() {
  pwm.setPWM(0, 0, pulseWidth(180));
  delay(1000);
}

Perfect we found all parameters we need to build the example program. For the sketch we use the Adafruit_PWMServoDriver library. If you do not know how to install a library you find here a tutorial.

First we include the libraries. Wire.h to use the I2C communication and Adafruit_PWMServoDriver to manage the CA9685 16-Channel Servo Driver board. Then we create an ServoDriver object called pwm with the right I2C HEX address. To find the right I2C HEX address you can use the I2C HEX code scanner in this article.

#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);

Next we define our variables. In this step you need the parameters MIN_PULSE_WIDTH and MAX_PULSE_WIDTH we identified prior. Nearly all servos use a frequency of 50Hz. You find this information on the data sheet of your motor.

#define MIN_PULSE_WIDTH 600
#define MAX_PULSE_WIDTH 2600
#define FREQUENCY 50

In the setup function the PWM is activated with the frequency of 50Hz.

void setup() 
{
  pwm.begin();
  pwm.setPWMFreq(FREQUENCY);
}

There is a function we need to calculate the analog value for the motor depending on the desired angle. First the pulse with is calculated between 0 degree and 180 degrees depending on the minimum and maximum pulse width. The analog value for the servo is calculated dividing the pulse wide with 1,000,000 to get us per second and multiplied with the frequency and 4096 for a 12 bit resolution.

int pulseWidth(int angle)
{
  int pulse_wide, analog_value;
  pulse_wide = map(angle, 0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
  analog_value = int(float(pulse_wide) / 1000000 * FREQUENCY * 4096);
  return analog_value;
}

In every single line of the loop function we can turn any connected servo between 0 degree and 180 degrees. Therefor the pwm.set function is used with 3 arguments:

  1. On which position of the PCA9685 is the servo connected. The numbers are printed on the board starting with 0 up to 15.
  2. Constant 0
  3. Function to specify the angle: pulseWidth(angle) where angle can be between 0 and 180.

We have to use a little delay that the servo can reach the desired position in each command.

void loop() {
  pwm.setPWM(0, 0, pulseWidth(0));
  pwm.setPWM(1, 0, pulseWidth(180));
  delay(1000);
  pwm.setPWM(4, 0, pulseWidth(0));
  delay(1000);
  pwm.setPWM(0, 0, pulseWidth(180));
  pwm.setPWM(1, 0, pulseWidth(90));
  delay(500);
  pwm.setPWM(4, 0, pulseWidth(180));
  delay(1000);
  pwm.setPWM(0, 0, pulseWidth(90));
  pwm.setPWM(1, 0, pulseWidth(0));
  delay(1000);
}

If you want to copy the whole script, use the following:

#include "Wire.h"
#include "Adafruit_PWMServoDriver.h"
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40);

#define MIN_PULSE_WIDTH 600
#define MAX_PULSE_WIDTH 2600
#define FREQUENCY 50

void setup() 
{
  pwm.begin();
  pwm.setPWMFreq(FREQUENCY);
}

int pulseWidth(int angle)
{
  int pulse_wide, analog_value;
  pulse_wide = map(angle, 0, 180, MIN_PULSE_WIDTH, MAX_PULSE_WIDTH);
  analog_value = int(float(pulse_wide) / 1000000 * FREQUENCY * 4096);
  return analog_value;
}

void loop() {
  pwm.setPWM(0, 0, pulseWidth(0));
  pwm.setPWM(1, 0, pulseWidth(180));
  delay(1000);
  pwm.setPWM(4, 0, pulseWidth(0));
  delay(1000);
  pwm.setPWM(0, 0, pulseWidth(180));
  pwm.setPWM(1, 0, pulseWidth(90));
  delay(500);
  pwm.setPWM(4, 0, pulseWidth(180));
  delay(1000);
  pwm.setPWM(0, 0, pulseWidth(90));
  pwm.setPWM(1, 0, pulseWidth(0));
  delay(1000);
}
Servo Arduino PCA9685
Servo NodeMCU PCA9685

Using multiple PCA9685 in a chain

As said before, it is possible to connect multiple PCA9685 in a chain to connect up to 992 servos with the 2 I2C pins on your microcontroller.
The following fritzing sketch shows you how to connect multiple PCA9685 boards.

Servo Arduino multiple PCA9685

You can use the sketch of the previous example and you only have to change the Hex address and create as many ServoDriver objects as you connect PCA9685 boards. The objects could be called pwm1, pwm2, etc. with the individual I2C HEX address. As mentioned before you can use the I2C HEX code scanner from this article to find the right addresses.

Conclusion

In this tutorial we learned a lot about servo motors. First we studied to theory about servos and the functionality. In the second part of the tutorial we got to the practical examples and started with basics examples to turn to motor up to 180 degrees and return to 0 degree. Also we succeeded to turn the servo depending on the position of a potentiometer. After the simple example we learned how to control multiple servos with the PCA9685 and how to connect multiple PCA9685 boards.
If you have any question about the functionality of the servo motor or to any example in this tutorial, please use the comment section below to ask you questions so that I can answer them directly.

4 Responses

Leave A Comment