Arduino Servo Smoothing - How to use a servo smoothing library Easily - it's not immediately obvious how to do it using the provided examples. Here you can find simple code to figure out what the smoothing profiles actually do.


First off, you're not really looking for Arduino Servo Smoothing, you're actually looking for Servo Easing! - There's a library for that name!

If you want to eliminate that annoyingly crude robot-jerky-movement from your animatronic creations - or perhaps you want your new pan-and-tilt project to move a bit more smoothly; this page will show you how.

What is Arduino Servo Smoothing (Easing)?


When you command your servo to move to a specific position it applies maximum torque to get there so it arrives in the shortest possible time.

There's no gentle start acceleration, no deceleration, or slow to stop at the final position - it just busts a gut to get there with no control on your part - except to specify the final angle.

Arduino Servo Smoothing or Servo Easing addresses this problem by using fine control of the position of the servo over time.

So instead of directly setting your desired "Final position angle", the code applies a transformation algorithm and at each time step, commands the servo to adopt a small step change angle.

This is Arduino Servo Smoothing but there's not just one transformation algorithm; you can choose between many types.

Libraries

For this code for Arduino servo smoothing you need to install the library:

     ServoEasing

Types of Profiles for Servo Easing

The easing profiles are:

Linear - Movement occurs at a constant rate from start to finish. This corresponds to no easing.

Quadratic - Movement follows a quadratic function, providing a smooth acceleration and deceleration. This is a parabolic curve.

Cubic - Movement follows a cubic function, providing a smoother and more gradual change than quadratic easing.

Quartic - Movement follows a quartic function,  providing an even smoother change than cubic easing.

Circular - Movement follows a circular path based on the angle, rather than linear acceleration/deceleration. Starts slow then fast to mid point, then slows to end.

Back - Overshoots the target briefly before coming back, simulating an elastic/spring movement - also pre-under shoots from the start.

Elastic - Combines effects of sine and back for a bounced oscillating movement, as if the movement was propelled by an elastic force. Does a bit of an oscillating movement at the start and end of movement around the final position.

Bounce - Creates a bounced recoil effect at the end of the movement, with the servo snapping to a stop with a small bounce - does several damped oscillations to final position.

Precision - Provides very smooth, fine-grained control that is useful for precise positioning in small incremental steps.

User - Allows a custom easing function to be defined by the user, providing full control over the speed curve of the movement. This is useful for implementing unique or complex easing behaviors.

Controlling Arduino Servo Smoothing profiles

Selecting a profile is controlled by the following definitions one for each of the profiles described above (in the example OneServo.ino on github)

/*
 * Specify which easings types should be available.
 * If no easing is defined, all easings are active.
 * This must be done before the #include "ServoEasing.hpp"
 */
//#define ENABLE_EASE_QUADRATIC
#define ENABLE_EASE_CUBIC
//#define ENABLE_EASE_QUARTIC
//#define ENABLE_EASE_SINE
//#define ENABLE_EASE_CIRCULAR
//#define ENABLE_EASE_BACK
//#define ENABLE_EASE_ELASTIC
//#define ENABLE_EASE_BOUNCE
//#define ENABLE_EASE_PRECISION
//#define ENABLE_EASE_USER

In the code example later on below these are not defined at all meaning that (as the text above says):  " If no easing is defined, all easings are active."

Why do you care about that? - its simply that having unused code may use up memory space. So to save space uncomment the ones you use (as in the code above).

To select a profile in your code you write:

ServoEasing servo; // Object instantiation
...
servo.setEasingType(EASE_QUADRATIC_IN_OUT);

Here is the code defined in ServoEasing.h just for the Quadratic operation - you use these definitions as in the code above - to select a start/end operation:

#define EASE_QUADRATIC_IN       0x01
#define EASE_QUADRATIC_OUT      0x41
#define EASE_QUADRATIC_IN_OUT   0x81
#define EASE_QUADRATIC_BOUNCING 0xC1

Further to control the start and end operations there are four suffixes:

_IN     
_OUT    
_IN_OUT 
_BOUNCING

These are definition suffix modifiers that affect the operation of the easing movement and can be applied to most easing profiles - some such as PRECISION only have PRECISION_OUT.

_IN              - Applies easing to the start of the movement.
_OUT           - Applies easing to the end of the movement.
_IN_OUT      - Applies easing to both start and end movements.
_BOUNCING  - After applying easing to the final position returns to the original position.

Simple sketch for Easing

The following code shows you how to choose a single easing profile and get the servo going for two servos. If you want to see what different profiles do then use the sketch later on, that allows you to test different profiles interactively.

This code shown below shows a single profile called 'Back' which I like; it gives you a natural movement, and moves the servo as if it were a physical needle that has inertia, so it overshoots the final position before coming slowly to its final position.

For each of the 10 definitions above that start with the words "ENABLE" you take the text from the word "EASE" and add one of the suffixes above. Commonly you'll use _IN_OUT. Note: not all profiles allow every suffix. You can observe which ones by looking at the test code later on.

For this code you need the following defintion to set the profile:

    EASE_BACK_IN_OUT

Here in out means apply the back movement to both the in-bound and out-bound servo movement.


// Copyright John Main: TronicsBench.com // Free for use in non- commercial projects.
#include <Servo.h> #include <ServoEasing.hpp> #define DEFAULT_MICROSECONDS_FOR_0_DEGREE 500 #define DEFAULT_MICROSECONDS_FOR_180_DEGREE 2500 #define NUM_SERVOS 2
static byte direction = 0; static int servoSpeed = 100; ServoEasing servo[2]; void setup() { servo[0].attach( 9, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE, -90, 90); servo[1].attach(10, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE, -90, 90); for(int i=0; i<NUM_SERVOS; i++) servo[i].setEasingType(EASE_BACK_IN_OUT); } void startServos(void) { setSpeedForAllServos(servoSpeed); if (direction==0) { for (int i=0; i<NUM_SERVOS; i++ ) servo[i].setEaseTo(60); } else { for (int i=0; i<NUM_SERVOS; i++ ) servo[i].setEaseTo(-60); } synchronizeAllServosStartAndWaitForAllServosToStop(); direction = !direction; } void loop(void) { startServos(); delay(1000); }

servo-easing-simple.ino

How can you use these profiles?


Here list of how these profiles are commonly used:

Natural motions

Sine and Circular are quite often used for natural, organic movement such as simulation movement of living creatures.

  • Sine - For elastic/spring-like overshoot movements.
  • Circular - For rotating, winding, movements that follow a circular path.
  • Back - For elastic/spring-like overshoot movements.

Generic acceleration/deceleration

These are used for simple direct movements from A to B in a smooth manner; most often used in robotics automation.

  • Linear - Constant speed animation over duration.
  • Quadratic - Gentle acceleration/deceleration.
  • Cubic - More dramatic acceleration/deceleration.
  • Quartic - Very steep acceleration/deceleration.

Physical Simulation

  • Elastic - For simulating a stick needle movement at start and end.
  • Bounce - For simulating an overshoot past the desired position - then bouncing back.
  • Precision - For accurate positioning.

Interactive test code

A slight Pain!

From a programming point of view, the specification of coding profiles as definitions has a consequence for writing test code:

Definitions are items that are pre-processor elements. The compiler applies all definitions (and macro-definitions) before processing any other elements in your file. So for the definition of EASE_QUADRATIC_IN_OUT, in the above code, the compiler will see:

    servo.setEasingType(0x81);

The code 0x81 is specific to the setup of the other code and controls it - but you don't need to know it - you can look at the code and figure it out if you want.

From a programming point of view where you want to see the different effects of the easing profiles this is a problem. It's a problem because you don't know how to construct the magic number 0x81. You can see that the definitions use a binary number to select from the different options (so you could figure it out) - but you would have to figure it out for all the different options of all the easing profiles.

The code has been constructed this way as you will only want to use a specific profile and no other. The code could have been constructed with a parameter based method i.e. you could have had functions:

    servo.setEasingType(int easeType); // Base ease type desired - fake function.
    servo.setEasingMod(byte modifier); // In,Out,InOut,Bounce sel. -fake function.

Allowing easy changing between Easing profiles...but it was not. So test code must elaborate all possible operations i.e. all definitions must be written out to select a modifier.

Test code has:
void setServoProfile_IN(EasingProfileEnum_t newProfile) {
   all definitions with _IN at end
}

void setServoProfile_OUT(EasingProfileEnum_t newProfile) {
  <  all definitions with _OUT at end >
}
etc.

What does this mean?


This is probably the only place that you will find test code for the servoEasing library that lets you switch easily between profiles and suffix modifier profiles - because you need more code than normal. It means you can easily see the effects of a profile without having to reprogram the Arudino every time!

Some details on the code


Note: The range limits of the servo movement are set to ±60­ Degrees as some profiles such as back, overshoot the final position until slowly returning to the final position; so you need to allow angular room, beyond the final position, to allow that.

Note: The rate of movement is altered by the type of profile so some profiles need a slower overall speed (allowing the later fast movement e.g. elastic). Increase the speed too much and you won't be able to see the easing effect.

Arduino Servo Smoothing: Sketch for testing servo Easing

// Copyright John Main: TronicsBench.com
// Free for use in non- commercial projects.
#include <Servo.h>
#include <ServoEasing.hpp>

#define DEFAULT_MICROSECONDS_FOR_0_DEGREE 500
#define DEFAULT_MICROSECONDS_FOR_180_DEGREE 2500

ServoEasing servo[1];
byte currentServo = 0;
int servoSpeed = 90;
byte direction = 0;

typedef int EasingProfile_t ;

typedef enum EasingProfileEnum {
    Linear,
    Quadratic,
    Cubic,
    Quartic,
    Circular,
    Sine,
    Back,
    Elastic,
    Bounce,
    Precision,
    INVALID_PROFILE
} EasingProfileEnum_t;


#define INOUT 2
int suffix = INOUT;
EasingProfileEnum_t newProfile;

EasingProfileEnum_t profile0 = Linear;
EasingProfileEnum_t profile1 = Quadratic;

void setup() {
  // Initialize the serial port and servos
  Serial.begin(115200);

  Serial.println("Servo Easing Testing - single servo.");
  showHelp();

  servo[0].attach( 9, DEFAULT_MICROSECONDS_FOR_0_DEGREE, DEFAULT_MICROSECONDS_FOR_180_DEGREE, -90, 90);
}

void printSpeed(void) {
  Serial.print("Speed ");
  Serial.println(servoSpeed);
}

void serialCommand(void) {

  // Check for incoming serial data
  if (Serial.available() > 0) {
    // Read the incoming byte
    char incomingByte = Serial.read();

    if (isDigit(incomingByte)) {  // set profile
      newProfile = selectProfile(incomingByte);

      if (newProfile != INVALID_PROFILE) setServoProfile(newProfile, suffix);

      printProfile(newProfile);

    } else if (incomingByte == 'a') { // Set profile for servo A
      Serial.println("Selecting servo A");
      currentServo = 0;

    // } else if (incomingByte == '#') { // Set profile for servo B
    //   Serial.println("Selecting servo B");
    //   currentServo = 1;


    } else if (incomingByte == 'g') { // Go - toggle direction of movement.

      if (direction==0) {
        setSpeedForAllServos(servoSpeed);
        servo[0].setEaseTo(60.0f); // Use x.y with trailing f (to specify a floating point constant) to avoid compiler errors.
        synchronizeAllServosStartAndWaitForAllServosToStop();

      } else {
        setSpeedForAllServos(servoSpeed);
        servo[0].setEaseTo(-60.0f); // Use x.y with trailing f (to specify a floating point constant) to avoid compiler errors.
        synchronizeAllServosStartAndWaitForAllServosToStop();
      }
      direction = !direction;

    } else if (incomingByte == 'i') { // Set profile for servo B
        Serial.println("next In/Out suffix");
        suffix++;
        if(suffix>=4) suffix= 0;
        printSuffix(suffix);
        Serial.println();
        setServoProfile(newProfile, suffix);

    } else if (incomingByte == '\\') {
      servoSpeed = 30;
      printSpeed();

    } else if (incomingByte == 'z') {
      servoSpeed = 60;
      printSpeed();

    } else if (incomingByte == 'x') {
      servoSpeed = 90;
      printSpeed();

    } else if (incomingByte == 'c') {
      servoSpeed = 120;
      printSpeed();

    } else if (incomingByte == 'b') {
      servoSpeed = 200;
      printSpeed();

    } else if (incomingByte == 'n') {
      servoSpeed = 300;
      printSpeed();

    }  else if (incomingByte == 'm') {
      servoSpeed = 400;
      printSpeed();

    } else if (incomingByte == ',') {
      servoSpeed = 800;
      printSpeed();

    } else if (incomingByte == '?') {
      showHelp();

    }
  }
}

void loop() {

   serialCommand();

}

void showProfileHelp(void ) {
  Serial.println("Easing profile:");
  Serial.println("1. Linear");
  Serial.println("2. Quadratic");
  Serial.println("3. Cubic");
  Serial.println("4. Quartic");
  Serial.println("5. Sine");
  Serial.println("6. Circular");
  Serial.println("7. Back");
  Serial.println("8. Elastic");
  Serial.println("9. Bounce");
  Serial.println("0. Precision");
}

void showHelp(void ) {
  showProfileHelp();
  Serial.println("?: this help");
  Serial.println("g: Go = toggle -60 to 60 Degrees");
  Serial.println("1 ~ 9 & '0': Set profile.");
  Serial.println("z ~ ',': Set speed \\ =30 -v slow.");
  Serial.println("i: Change profile mod ; in,out, inout, bouncing");
}

// Helper function to select an easing profile
// if not a number input is ignored and returns INVALID_PROFILE as indicator.
EasingProfileEnum_t selectProfile(char incomingByte) {

  // Set the easing profile based on the incoming byte
  if (incomingByte == '1') {
    return Linear;
  } else if (incomingByte == '2') {
    return Quadratic;
  } else if (incomingByte == '3') {
    return Cubic;
  } else if (incomingByte == '4') {
    return Quartic;
  } else if (incomingByte == '5') {
    return Sine;
  } else if (incomingByte == '6') {
    return Circular;
  } else if (incomingByte == '7') {
    return Back;
  } else if (incomingByte == '8') {
    return Elastic;
  } else if (incomingByte == '9') {
    return Bounce;
  } else if (incomingByte == '0') {
    return Precision;
  } return INVALID_PROFILE;
}


void setServoProfile(EasingProfileEnum_t newProfile, int suffix) {

  switch(suffix) {

    case 0: setServoProfile_IN(newProfile);
      break;

    case 1:  setServoProfile_OUT(newProfile);
      break;

    case 2: setServoProfile_IN_OUT(newProfile);
      break;

    case 3:  setServoProfile_BOUNCING(newProfile);
      break;
  }
}


void setServoProfile_IN_OUT(EasingProfileEnum_t newProfile) {
  switch (newProfile) {
    case Linear:
      servo[currentServo].setEasingType(EASE_LINEAR); // Is the same for IN_OUT
      break;

    case Quadratic:
      servo[currentServo].setEasingType(EASE_QUADRATIC_IN_OUT);
      break;

    case Cubic:
      servo[currentServo].setEasingType(EASE_CUBIC_IN_OUT);
      break;

    case Quartic:
      servo[currentServo].setEasingType(EASE_QUARTIC_IN_OUT);
      break;

    case Circular:
      servo[currentServo].setEasingType(EASE_CIRCULAR_IN_OUT);
      break;

    case Sine:
      servo[currentServo].setEasingType(EASE_SINE_IN_OUT);
      break;

    case Back:
      servo[currentServo].setEasingType(EASE_BACK_IN_OUT);
      break;

    case Elastic:
      servo[currentServo].setEasingType(EASE_ELASTIC_IN_OUT);
      break;

    case Bounce:
      servo[currentServo].setEasingType(EASE_BOUNCE_OUT); // No in prob as you want at end
      break;

    case Precision:
      servo[currentServo].setEasingType(EASE_PRECISION_OUT); // Meaining of precision?
      break;

    default:
      printf("Invalid easing profile");
      break;
  } // switch
}

void setServoProfile_IN(EasingProfileEnum_t newProfile) {
  switch (newProfile) {
    case Linear:
      servo[currentServo].setEasingType(EASE_LINEAR); // Is the same for IN_OUT
      break;

    case Quadratic:
      servo[currentServo].setEasingType(EASE_QUADRATIC_IN);
      break;

    case Cubic:
      servo[currentServo].setEasingType(EASE_CUBIC_IN);
      break;

    case Quartic:
      servo[currentServo].setEasingType(EASE_QUARTIC_IN);
      break;

    case Circular:
      servo[currentServo].setEasingType(EASE_CIRCULAR_IN);
      break;

    case Sine:
      servo[currentServo].setEasingType(EASE_SINE_IN);
      break;

    case Back:
      servo[currentServo].setEasingType(EASE_BACK_IN);
      break;

    case Elastic:
      servo[currentServo].setEasingType(EASE_ELASTIC_IN);
      break;

    case Bounce:
      servo[currentServo].setEasingType(EASE_BOUNCE_OUT); // No in prob as you want at end
      break;

    case Precision:
      servo[currentServo].setEasingType(EASE_PRECISION_OUT); // Meaining of precision?
      break;

    default:
      printf("Invalid easing profile");
      break;
  } // switch
}

void setServoProfile_OUT(EasingProfileEnum_t newProfile) {
  switch (newProfile) {
    case Linear:
      servo[currentServo].setEasingType(EASE_LINEAR); // Is the same for IN_OUT
      break;

    case Quadratic:
      servo[currentServo].setEasingType(EASE_QUADRATIC_OUT);
      break;

    case Cubic:
      servo[currentServo].setEasingType(EASE_CUBIC_OUT);
      break;

    case Quartic:
      servo[currentServo].setEasingType(EASE_QUARTIC_OUT);
      break;

    case Circular:
      servo[currentServo].setEasingType(EASE_CIRCULAR_OUT);
      break;

    case Sine:
      servo[currentServo].setEasingType(EASE_SINE_OUT);
      break;

    case Back:
      servo[currentServo].setEasingType(EASE_BACK_OUT);
      break;

    case Elastic:
      servo[currentServo].setEasingType(EASE_ELASTIC_OUT);
      break;

    case Bounce:
      servo[currentServo].setEasingType(EASE_BOUNCE_OUT); // No in prob as you want at end
      break;

    case Precision:
      servo[currentServo].setEasingType(EASE_PRECISION_OUT); // Meaining of precision?
      break;

    default:
      printf("Invalid easing profile");
      break;
  } // switch
}

void setServoProfile_BOUNCING(EasingProfileEnum_t newProfile) {
  switch (newProfile) {
    case Linear:
      servo[currentServo].setEasingType(EASE_LINEAR); // Is the same for _IN_OUT
      break;

    case Quadratic:
      servo[currentServo].setEasingType(EASE_QUADRATIC_BOUNCING);
      break;

    case Cubic:
      servo[currentServo].setEasingType(EASE_CUBIC_BOUNCING);
      break;

    case Quartic:
      servo[currentServo].setEasingType(EASE_QUARTIC_BOUNCING);
      break;

    case Circular:
      servo[currentServo].setEasingType(EASE_CIRCULAR_BOUNCING);
      break;

    case Sine:
      servo[currentServo].setEasingType(EASE_SINE_BOUNCING);
      break;

    case Back:
      servo[currentServo].setEasingType(EASE_BACK_BOUNCING);
      break;

    case Elastic:
      servo[currentServo].setEasingType(EASE_ELASTIC_BOUNCING);
      break;

    case Bounce:
      servo[currentServo].setEasingType(EASE_BOUNCE_OUT); // Replace _IN_OUT with _BOUNCING
      break;

    case Precision:
      servo[currentServo].setEasingType(EASE_PRECISION_OUT); // Meaning of precision?
      break;

    default:
      printf("Invalid easing profile");
      break;
  } // switch
}

void printProfile (EasingProfileEnum_t profile) {
   switch (profile) {
        case Linear:
            Serial.println("Linear");
            break;
        case Quadratic:
            Serial.println("Quadratic");
            break;
        case Cubic:
            Serial.println("Cubic");
            break;
        case Quartic:
            Serial.println("Quartic");
            break;
        case Circular:
            Serial.println("Circular");
            break;
        case Sine:
            Serial.println("Sine");
            break;
        case Back:
            Serial.println("Back");
            break;
        case Elastic:
            Serial.println("Elastic");
            break;
        case Bounce:
            Serial.println("Bounce");
            break;
        case Precision:
            Serial.println("Precision");
            break;
        case INVALID_PROFILE:
            Serial.println("INVALID_PROFILE");
            break;
        default:
            Serial.println("Unknown");
            break;
    }
}


void printSuffix (int suffix) {
   switch (suffix) {
        case 0:
            Serial.print("_In");
            break;
        case 1:
            Serial.println("_OUT");
            break;
        case 2:
            Serial.println("_IN_OUT");
            break;
        case 3:
            Serial.println("_BOUNCING");
            break;

        default:
            Serial.println("Unknown");
            break;
    }
}

servo-easing-multi-cmd-initial.ino

Serial controls for the sketch

The controls for using the sketch are:

Easing profile:
1. Linear
2. Quadratic
3. Cubic
4. Quartic
5. Sine
6. Circular
7. Back
8. Elastic
9. Bounce
0. Precision
?: this help
g: Go = toggle -60 to 60 Degrees
1 ~ 9 & '0': Set profile.
z ~ ',': Set speed \ =30 -v slow.
i: Change profile mod ; in, out, inout, bouncing

Bonus Sketch Exclusive Content

For the following sketch you can drive up to 3 servos at the same time. This is a more developed piece of code - from the above code - that allows you to set the speed using command followed by a number, set profiles for each servo or even turn each one off. You can also individually set the maximum extend angle different for each servo as well.

Here's the complete command set:

Servo Easing Testing - Multi servos.
Easing profile:
1. Linear
2. Quadratic
3. Cubic
4. Quartic
5. Sine
6. Circular
7. Back
8. Elastic
9. Bounce
0. Precision
?: this help.
g: Go = single mode toggle default: -60 to 60 Degrees.
t - toggle continuous.
1 ~ 9 & '0': Select profile.
i: Toggle inout profile - some don't allow all (use 'd').
a,b,c : Choose servo.
s<num> set speed for all servos.
r - Reset and stop servos - set to 0 degrees (center).
o - Toggle current servo on/off.
m<num> - Set current servo angle.
d - show data.

To reveal the download link signup to the newsletter in the form below.

You can download the program (servo-easing-multi-cmd.zip) from this link.

The code is command line based so after each command hit the enter key to execute the command.

First of all us the 't' command to get all the servos moving. Now choose a number key to see the different easing profile effects. To stop movement enter 't' again. You can enter the help key '?' to see the profiles and commands.

To see what the current setup of all the servos use the 'd' command.

When you 1st start the servo speed is set to 100 (you can change it with the s command). Try going through all the servo easings 1~0,'0'.

For the easing profiles 8,9,0 this speed is a bit too high so set the speed to 70 's70', to see them better.

Note that the bounce easing profile is different to the suffix _BOUNCE control. the former means the servo comes to rest at its final position as if it were bouncing on the final position. _BOUNCE means the servo returns to its original position.

Turn off movement 't'.
Use command 'i' until BOUNCING is shown.
Set to center position, 'r'.
Select easing profile '4' - Quartic for Servo A.
Set speed 120 - 's120'.
Now use single execute 'g' command.

You will clearly see that the servo returns to the 0 degree position i.e. straight ahead.




Written by John Main who has a degree in Electronic Engineering.

Note: Parts of this page were written using chatgpt as a research assistant.


Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.



Privacy Policy | Contact | About Me

Site Map | Terms of Use