All The WTF’s of Servo Motors & Programming

all things servo motors

So… you’ve got a project that uses servos. Perhaps you have a driver, great! If you’re like me, you’ve probably started out with a failure or two, and that’s fine if you can afford to fail; [regardless, it’s a lesson you’ll learn and grow from] but, for those who are more organized, less chaotic –who prefer to be educated beforehand instead of diving in hands-on– this is for you. (It’s also a quick reference guide I created for myself)

What is a Servo Motor?

A servo motor is a small, powerful motor used for precise control of position and speed. It has three main parts:

  • DC motor: The actual motor that provides the movement.
  • Gearbox: Reduces the motor’s speed and increases torque.
  • Control circuit: Ensures the motor moves to the desired position.

  • range of motion: how far the servo can rotate
  • load type: what the servo will be used for, as different weights / materials have different requirements
  • torque rating: how much mechanical force the servo can handle safely
  • acceleration, deceleration, and running torque: these factors directly influence motor speeds

What I have found is to use your hands and carefully turn the shaft as far clockwise as it’s able to for the starting position. This all depends on the code, however, and whether the code begins with 1ms and gradually increases the pulse to 2ms, or whether the shaft position moves from right to left or from left to right. Also, this depends on the motor, as some makes/ models have different pulse widths and degree ranges.

The biggest thing here is to continuously test the code and tweak it, you don’t want the shaft hitting it’s stopping point too hard.

Neutral position

The neutral position is at 90 degrees, where the servo has the same amount of potential rotation in both directions.

Rotation detector

A rotation detector (encoder) on the back shaft of the motor detects the position and speed of the rotor.

They do NOT all rotate the same!

Servo motors can rotate within a limited range, usually 180ยฐ or 360ยฐ, depending on the specific motor brand / type. You can find the range of your motor’s rotation in the manufacturer’s datasheet or documentation for it.

Not all motors rotate within a limited range.

Typical / hobbyist servo motors:

These motors typically have a limited rotation of 0-180 degrees (half circle). However, some older or specialty models could have a smaller (or larger) range.

Continuous rotation servo motors:

These motors can turn endlessly in either direction, providing full 360 degree uninterrupted rotation and range of motion (like simple DC motors). They are usually found in conveyor systems or robotics.

Some (not all!) servo motors have an end-stop mechanism, a built-in “thing” which prevents the servo from rotating past the range of the potentiometer. This “thing” is usually a physical plastic part or component attached to a gear inside the motor that -when reaching it’s start / end points- stops the servo.

In cases where the motor is programmed to move past its range, you can hear a slight buzzing noise occurring as the motor’s end-stop mechanism is activated. This is called a positioning error. In simpler terms, the motor makes a buzzing sound like it’s seized up (because it basically is) until programmed to move the opposite way from its limit / end-point. If it is told to keep moving past its end of range (on either side), the gears are given a continuous signal / pulse to move, and the motor tries to, but the stop mechanism is engaged so it can’t.

How to find / test / measure the servo’s range

If unable to locate specific documentation from the manufacturer on the servo motor, you can find it’s range in other ways.

Write and run a code to vary the pulse-width from 500 microseconds to 2.5 milliseconds. If the motor stops moving and makes a buzzing sound, it’s probably programmed past it’s range, so make incremental adjustments (by changing the range in the code) until it flows smoothly in either direction with no seizing. See: positioning error
Physically (and carefully) rotate the motor shaft by hand to see its full movement range.
Find libraries or code specific to your motor and run those, observing the values within the code that work.
For an inconvenient & messy method for advanced users, you can obtain the servo’s position(s) by [digitally] opening and extracting the output value of the potentiometer inside the motor itself by connecting a wire to the output of the potentiometer to a microcontroller, and just read the analog input signal and/or print it to the serial monitor. The signal read correlates to the position of the servo.

Servo motors are ideally used for robotics and in areas where you need precise control over things like moving arms, wheels, or sensors.

How Does a Servo Motor Work?

Servo motors work by receiving a control signal (called PWM โ€“ Pulse Width Modulation) from a microcontroller. Simply put, the servo motors are controlled by the width of a pulse. This PWM signal controls the position of the shaft / angle of rotation:

  • PWM sends a series of electrical pulses. The length / width of the pulse tells the motor which position to move to. These pulses are calculated / distributed in anywhere between 500 microseconds to 1-2 milliseconds of signal width (1-2ms width pulses being the typical for a 180 degree servo) with pulses sent every 20 milliseconds or so (depends on your make/model).
  • The wider the pulse, the wider the turn / rotation / angle
    • Short pulse-width โ†’ Rotate to small angle.
    • Wide pulse-width โ†’ Rotate to large angle.

For example, in a 180ยฐ servo:

  • A 1 millisecond pulse makes the motor move to 0ยฐ (beginning / starting-point of its range
  • A 1.5 millisecond pulse moves it to the 90ยฐ (middle) of its range
  • A 2 millisecond pulse moves it to 180ยฐ (full rotation) end of its range

Wiring a Servo to Arduino / ESP32

Most servo motors have three wires:

  1. Ground (GND) โ€“ usually black or brown.
  2. Power (VCC) โ€“ usually red, connects to 5V or 3.3V (depending on your board).
  3. Signal (PWM control) โ€“ usually yellow or orange, connects to a GPIO pin on your microcontroller.

Wiring the circuit (important!!)

If you’re using more than one servo, you’ll most likely need to use an external power supply. You’ll need to connect all of the grounds (ESP32, servo, and external power). I repeat. CONNECT. ALL. GROUNDS. TOGETHER. OR ELSE!!!! [WILL UPDATE: //insert more ref. & context upon update here]

For a small servo like the S0009, you can connect the servo to the ESP32 directly. The connections are: 

  • GND: Connect the servo’s ground wire to the ESP32’s GND pin 
  • Power: Connect the servo’s power wire to the ESP32’s VIN pin 
  • Signal: Connect the servo’s signal wire to the ESP32’s GPIO 13 or any PWM pin 

Tips for Working with Servos

  • Power: Servos can draw more current under load, so if itโ€™s jerky or unstable, use an external power source (especially with multiple servos).
  • Limitations: Standard hobby servos only rotate 180ยฐ, but continuous rotation servos spin fully. Check your servo type.
  • Calibration: The values for 0ยฐ, 90ยฐ, and 180ยฐ might vary slightly for different servos. Test and adjust the PWM values as needed.

Common Issues

  • Servo jittering or not moving: This could be due to low power. Try using a separate power source (e.g., 5V battery) rather than relying on the Arduino/ESP32 power pin.
  • Incorrect rotation: Ensure that the PWM signal values are correctly set (usually between 500 to 2500 microseconds [or typically 1 millisecond to 2ms] for a typical 180ยฐ servo).


**It’s always necessary to use a motor driver**

…and NOT connect a motor directly to the board; however, for quick testing / trial purposes only, it’s doable to connect ONE motor directly to the board. It most certainly is not advisable, as this is a recipe for disaster & board damages.

Wiring a Servo to Arduino:
  • VCC (red wire) โ†’ Connect to 5V pin on Arduino.
  • GND (black wire) โ†’ Connect to GND on Arduino.
  • Signal (yellow/orange wire) โ†’ Connect to any digital PWM pin (e.g., D9).
Wiring a Servo to ESP32 (specifically the DevKit V1):
  • VCC (red wire) โ†’ Connect to 3.3V pin.
  • GND (black wire) โ†’ Connect to GND pin.
  • Signal (yellow/orange wire) โ†’ Connect to any GPIO pin (e.g., GPIO 18).

Arduino makes controlling servos easy using the Servo library. Follow these steps:

Step 1: Install Servo Library

The Servo.h library allows you to control servos with just a few lines of code.

  1. Open the Arduino IDE.
  2. Go to Sketch > Include Library > Manage Libraries…
  3. Search for Servo and install it.

Step 2: Write Basic Servo Code

Hereโ€™s a simple code to make the servo rotate to different angles:

#include <Servo.h>

Servo myServo;  // Create a servo object to control a servo

void setup() {
  myServo.attach(9);  // Attach the servo to pin D9 (PWM pin)
}

void loop() {
  myServo.write(0);   // Move servo to 0 degrees
  delay(1000);        // Wait for 1 second
  myServo.write(90);  // Move servo to 90 degrees (middle position)
  delay(1000);        // Wait for 1 second
  myServo.write(180); // Move servo to 180 degrees
  delay(1000);        // Wait for 1 second
}

How It Works:

  • myServo.attach(9); โ€“ This tells Arduino which pin the servo signal is connected to (D9 in this case).
  • myServo.write(angle); โ€“ Moves the servo to the specified angle (e.g., 0ยฐ, 90ยฐ, or 180ยฐ).
  • delay(1000); โ€“ Pauses the program for 1 second, allowing time for the servo to move.

The ESP32 uses a similar approach but with some differences. Instead of using the Servo library, we can use PWM directly through the ESP32 PWM functions.

Hereโ€™s how to program a servo using ESP32:

Step 1: Initialize PWM

ESP32 has 16 PWM channels, and we use one of them to control the servo motor.

The easiest (as of my knowledge to this date) way to program and work with these motors is to use a motor driver. The code below is an example of motor controls / functions utilizing the PCA9685 motor driver module, and Adafruit did a wonderful job laying out documentation / user-friendly hints to work with.

You’ll have to download the libraries, just type ‘PCA9685’ (without the quotes of course) in the Arduino IDE libraries section and download Adafruit’s. If it asks you to install other libraries with it, do so. They’re needed.

I changed the SERVOMAX/MIN and the USMAX/MIN variables to a smaller range from the original because I noticed on one of my 3 servos, the largest one (STD TS-53 by Tower Hobbies) was making a buzzing sound as it reached one end. I figured it was pressing too hard against its stopping point and needed some space. Just for context, the other 2 used are micro 9g SG90.

/*************************************************** 
  This is an example for our Adafruit 16-channel PWM & Servo driver
  Servo test - this will drive 8 servos, one after the other on the
  first 8 pins of the PCA9685
  
  These drivers use I2C to communicate, 2 pins are required to  
  interface.

  Adafruit invests time and resources providing this open source code, 
  please support Adafruit and open-source hardware by purchasing 
  products from Adafruit!

  Written by Limor Fried/Ladyada for Adafruit Industries.  
  BSD license, all text above must be included in any redistribution
 ****************************************************/

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

// called this way, it uses the default address 0x40
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver();
// you can also call it with a different address you want
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41);
// you can also call it with a different address and I2C interface
//Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x40, Wire);

// Depending on your servo make, the pulse width min and max may vary, you 
// want these to be as small/large as possible without hitting the hard stop
// for max range. You'll have to tweak them as necessary to match the servos you
// have!
#define SERVOMIN  150 // This is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  600 // This is the 'maximum' pulse length count (out of 4096)
#define USMIN  700 // This is the rounded 'minimum' microsecond length based on the minimum pulse of 150
#define USMAX  2200 // This is the rounded 'maximum' microsecond length based on the maximum pulse of 600
#define SERVO_FREQ 50 // Analog servos run at ~50 Hz updates

// our servo # counter
uint8_t servonum = 0;

void setup() {
  Serial.begin(115200);
  Serial.print("8 channel Servo test!");

  pwm.begin();
  /*
   * In theory the internal oscillator (clock) is 25MHz but it really isn't
   * that precise. You can 'calibrate' this by tweaking this number until
   * you get the PWM update frequency you're expecting!
   * The int.osc. for the PCA9685 chip is a range between about 23-27MHz and
   * is used for calculating things like writeMicroseconds()
   * Analog servos run at ~50 Hz updates, It is importaint to use an
   * oscilloscope in setting the int.osc frequency for the I2C PCA9685 chip.
   * 1) Attach the oscilloscope to one of the PWM signal pins and ground on
   *    the I2C PCA9685 chip you are setting the value for.
   * 2) Adjust setOscillatorFrequency() until the PWM update frequency is the
   *    expected value (50Hz for most ESCs)
   * Setting the value here is specific to each individual I2C PCA9685 chip and
   * affects the calculations for the PWM update frequency. 
   * Failure to correctly set the int.osc value will cause unexpected PWM results
   */
  pwm.setOscillatorFrequency(27000000);
  pwm.setPWMFreq(SERVO_FREQ);  // Analog servos run at ~50 Hz updates

  delay(10);
}

// You can use this function if you'd like to set the pulse length in seconds
// e.g. setServoPulse(0, 0.001) is a ~1 millisecond pulse width. It's not precise!
void setServoPulse(uint8_t n, double pulse) {
  double pulselength;
  
  pulselength = 1000000;   // 1,000,000 us per second
  pulselength /= SERVO_FREQ;   // Analog servos run at ~60 Hz updates
  Serial.print(pulselength); 
  Serial.println(" us per period"); 
  pulselength /= 4096;  // 12 bits of resolution
  Serial.print(pulselength); 
  Serial.println(" us per bit"); 
  pulse *= 1000000;  // convert input seconds to us
  pulse /= pulselength;
  Serial.println(pulse);
  pwm.setPWM(n, 0, pulse);
}

void loop() {
  // Drive each servo one at a time using setPWM()
  Serial.println(servonum);
  for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);
  for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
    pwm.setPWM(servonum, 0, pulselen);
  }

  delay(500);

  // Drive each servo one at a time using writeMicroseconds(), it's not precise due to calculation rounding!
  // The writeMicroseconds() function is used to mimic the Arduino Servo library writeMicroseconds() behavior. 
  for (uint16_t microsec = USMIN; microsec < USMAX; microsec++) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);
  for (uint16_t microsec = USMAX; microsec > USMIN; microsec--) {
    pwm.writeMicroseconds(servonum, microsec);
  }

  delay(500);

  servonum++;
//for 3 servos on the 16-channel PCA motor driver module
  if (servonum > 2) servonum = 0; 
}

How It Works:

  • the void loop() runs through each function one after the other and then repeats.
  • the first function moves the motor (left or right, depends on how you’re facing the motor) one way, the next function moves it the other / opposite way.
  • the two functions below the first two are the same back and forth motion, only using a different algorithm or calculation for the PWM signals, which they (as documented in their comments) advise is less exact or precise due to rounding of numbers.

Summary

  • Servos are great for precise movements, and they are controlled with PWM signals.
  • Arduino has a simple Servo library that makes controlling them easy, while ESP32 uses PWM functions for more advanced control.
  • Wiring is straightforward: power, ground, and a signal pin for PWM control.
  • Both boards need proper PWM frequency (50 Hz) for smooth servo movement.

About