Controlling RC servos with Arduino

Controlling RC servos with Arduino is relatively simple, at least when you use Arduino’s servo library. But the default timing may be a bit off for your application and the library seems a bit useless, or is there a simple way to fix this?

How is a RC servo controlled

RC servo
RC servo

For the electronics part, RC servos are controlled with a PWM (Pulse Width Modulation) signal. That means the servo will receive a pulse every x milliseconds, width a variable pulse width that corresponds with the servos position (or angle). Small pulse width for small angle, large pulse width for large angle.

There are two main categories in the RC servos

  • Normal servos
  • High Resolution Servos (HRS)

Normal servo

The normal, or standard servos, use a PWM of 50Hz. That means the servo will receive 50 pulses per second, or a pulse every 20 milliseconds, to command it to travel to a certain position or angle.

HRS servos

The other type is called the HRS or “High Resolution Servos” that use a PWM of 250Hz. That is a pulse signal every 4 milliseconds.

Timing diagram 50Hz vs 250Hz
Timing diagram 50Hz vs 250Hz

HRS servos can be used with 50Hz PWM signals, they will just work like a normal servo. However, normal servos cannot be controlled with a 250Hz PWM signal.

Centre position

The centre position of a servo is normally between a pulse width of 1500µs to 1520µs, depending on the servo’s brand and model. However, there are some servos that use 560µs, 760µs, 960µs as their centre position. These are “Narrow Band Servos”, and are most commonly used with tail gyros and FBL systems (FlyBarLess systems in RC helicopters). They may be over-steered and even damaged when controlled with the 1500µs signal. So be careful if you happen to get your hands on one of these servos.

The Arduino servo library

The servo library from Arduino only outputs a 50Hz PWM, so basically using anything more performant than a standard servo with this library, is pretty pointless. The pulse width however can be easily adjusted.

Using the library with default settings

Servo on an Arduino Uno
Servo on an Arduino Uno

To use the Arduino servo library with default settings, use the below sample code.
In Set the pin number to which the servo control wire is connected.
By default this is set to pin 2.

// Set servo pin
#define pinServo 2

Sample code for default library

// Include servo library
#include <Servo.h>;

// Set servo pin
#define pinServo 2

// Create servo object
Servo myServo;

/****************************************************************
      SETUP
****************************************************************/
void setup()
{
  Serial.begin(115200);
  while (!Serial) {
    ; // Wait for Serial
  }
  Serial.println("--- Serial monitor started ---");

  Serial.println("Connecting servo");
  myServo.attach(pinServo);  // Connect servo
}

/****************************************************************
      LOOP
****************************************************************/
void loop()
{
  Serial.println("Travel to 0°");
  myServo.write(0);     // Travel to 0° angle
  delay(1000);          // wait 1s (1000ms)
  Serial.println("Travel to 90°");
  myServo.write(90);    // Travel to 90° angle (centre point)
  delay(1000);
  Serial.println("Travel to 180°");
  myServo.write(180);   // Travel to 180° angle
  delay(1000);
}
// END

With the command servo.attch(<pin>) the servo will be controlled from the pin specified, and the angles will correspond to the pulse width specified as

  • 544µs for 0° angle
  • 2400µs for 180° angle

That will leave the centre position (90°) at a pulse width of 1472µs.

For most standard servos, this will not work very well and may result in several problems:

  • If the servo was already pre-assembled with the levers, the centre position will not be 90°
  • The far edges (0° and 180°) will overshoot the servo and may even damage it
  • Accuracy may be insufficient

Default values for controlling a standard servo are usually in these ranges

MinimumCentreMaximum
1100µs1500µs1900µs
900µs1500µs2100µs
920µs1520µs2120µs

Custom settings

Luckily, the servo library has a solution for this: servo.attach(<pin>, <min µs>, <max µs>)

Three servos on Arduino Uno
Three servos on Arduino Uno

Set the pin numbers to which the servo control wires are attached.
By default these are pins 2, 3 and 4.

// Define servo connections
#define pinServo1   2
#define pinServo2   3
#define pinServo3   4

Sample code for custom settings

// Include servo library
#include <Servo.h>;

// Define servo connections
#define pinServo1   2
#define pinServo2   3
#define pinServo3   4

// Create servo object
Servo myServo1;
Servo myServo2;
Servo myServo3;

/****************************************************************
      SETUP
****************************************************************/
void setup()
{
  // Connect servo and set min and max to 1000µs and 1900µs (1500µs = centre)
  myServo1.attach(pinServo1, 1100, 1900);	
  // Connect servo and set min and max to  900µs and 2100µs (1500µs = centre)
  myServo2.attach(pinServo2, 900, 2100);
  // Connect servo and set min and max to  920µs and 2120µs (1520µs = centre !)
  myServo3.attach(pinServo3, 920, 2120);
  
  // Move all servos to centre position (90°)
  myServo1.write(90);
  myServo2.write(90);
  myServo3.write(90);
}

/****************************************************************
      LOOP
****************************************************************/
void loop()
{
  ;
}
//END

As myServo1 and myServo2 have their centre position at 1500µs and myServo3 at 1520µs, when traveling to their centre position (90° angle) the pulse width for myServo3 will be 1520µs, rather than 1500µs as with myServo1 and myServo2.

Timing diagram 1500µs vs 1520µs
Timing diagram 1500µs vs 1520µs

Note: Looking at the diagram, you’ll also notice that the servos are controlled one by one, rather than all at the same time.

Write microseconds

There is another way of controlling the servo. You can directly specify the pulse width (in µs) using the function   servo.writeMicroseconds(<µs>)   instead of   servo.write(<angle>)

Sample code with writeMicroseconds

// Include servo library
#include <Servo.h>;

// Define servo connections
#define pinServo1   2
#define pinServo2   3
#define pinServo3   4

// Create servo object
Servo myServo1;
Servo myServo2;
Servo myServo3;

/****************************************************************
      SETUP
****************************************************************/
void setup()
{
  // Connect servo and set min and max to 1000µs and 1900µs (1500µs = centre)
  myServo1.attach(pinServo1, 1100, 1900);	
  // Connect servo and set min and max to  900µs and 2100µs (1500µs = centre)
  myServo2.attach(pinServo2, 900, 2100);
  // Connect servo and set min and max to  920µs and 2120µs (1520µs = centre !)
  myServo3.attach(pinServo3, 920, 2120);

  myServo1.writeMicroseconds(1500);  // Centre position
  myServo2.writeMicroseconds(1500);  // Centre position
  myServo3.writeMicroseconds(1500);  // This won't be the centre position
}

/****************************************************************
      LOOP
****************************************************************/
void loop()
{
  ;
}
//END

Because the position was directly set specifying the pulse width rather than the angle, all three servos now have a pulse width of 1500µs, even myServo3, which will now be slightly off-centre.

Timing diagram microseconds

Final note

The servo library is a very easy to use library, without limiting the possibility of fine-tuning the servo positions. In the RC world, every transmitter has a function to “trim” the servo, so the centre can be perfectly set to a 90° angle. With the servo library now understood properly, you can make the same kind of mechanism so all servo centres are perfectly 90°.


References
Arduino Boards: Arduino Uno R3
Arduino Software: Arduino IDE
Arduino Reference: Servo library