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).
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:
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.
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
ServoEasingservo[1];
bytecurrentServo=0;
intservoSpeed=90;
bytedirection=0;
typedefintEasingProfile_t;
typedefenumEasingProfileEnum{
Linear,
Quadratic,
Cubic,
Quartic,
Circular,
Sine,
Back,
Elastic,
Bounce,
Precision,
INVALID_PROFILE
}EasingProfileEnum_t;
#define INOUT 2
intsuffix=INOUT;
EasingProfileEnum_tnewProfile;
EasingProfileEnum_tprofile0=Linear;
EasingProfileEnum_tprofile1=Quadratic;
voidsetup(){
// 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);
}
voidprintSpeed(void){
Serial.print("Speed ");
Serial.println(servoSpeed);
}
voidserialCommand(void){
// Check for incoming serial data
if(Serial.available()>0){
// Read the incoming byte
charincomingByte=Serial.read();
if(isDigit(incomingByte)){// set profile
newProfile=selectProfile(incomingByte);
if(newProfile!=INVALID_PROFILE)setServoProfile(newProfile,suffix);
printProfile(newProfile);
}elseif(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;
}elseif(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;
}elseif(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);
}elseif(incomingByte=='\\'){
servoSpeed=30;
printSpeed();
}elseif(incomingByte=='z'){
servoSpeed=60;
printSpeed();
}elseif(incomingByte=='x'){
servoSpeed=90;
printSpeed();
}elseif(incomingByte=='c'){
servoSpeed=120;
printSpeed();
}elseif(incomingByte=='b'){
servoSpeed=200;
printSpeed();
}elseif(incomingByte=='n'){
servoSpeed=300;
printSpeed();
}elseif(incomingByte=='m'){
servoSpeed=400;
printSpeed();
}elseif(incomingByte==','){
servoSpeed=800;
printSpeed();
}elseif(incomingByte=='?'){
showHelp();
}
}
}
voidloop(){
serialCommand();
}
voidshowProfileHelp(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");
}
voidshowHelp(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_tselectProfile(charincomingByte){
// Set the easing profile based on the incoming byte
if(incomingByte=='1'){
returnLinear;
}elseif(incomingByte=='2'){
returnQuadratic;
}elseif(incomingByte=='3'){
returnCubic;
}elseif(incomingByte=='4'){
returnQuartic;
}elseif(incomingByte=='5'){
returnSine;
}elseif(incomingByte=='6'){
returnCircular;
}elseif(incomingByte=='7'){
returnBack;
}elseif(incomingByte=='8'){
returnElastic;
}elseif(incomingByte=='9'){
returnBounce;
}elseif(incomingByte=='0'){
returnPrecision;
}returnINVALID_PROFILE;
}
voidsetServoProfile(EasingProfileEnum_tnewProfile,intsuffix){
switch(suffix){
case0:setServoProfile_IN(newProfile);
break;
case1:setServoProfile_OUT(newProfile);
break;
case2:setServoProfile_IN_OUT(newProfile);
break;
case3:setServoProfile_BOUNCING(newProfile);
break;
}
}
voidsetServoProfile_IN_OUT(EasingProfileEnum_tnewProfile){
switch(newProfile){
caseLinear:
servo[currentServo].setEasingType(EASE_LINEAR);// Is the same for IN_OUT
break;
caseQuadratic:
servo[currentServo].setEasingType(EASE_QUADRATIC_IN_OUT);
break;
caseCubic:
servo[currentServo].setEasingType(EASE_CUBIC_IN_OUT);
break;
caseQuartic:
servo[currentServo].setEasingType(EASE_QUARTIC_IN_OUT);
break;
caseCircular:
servo[currentServo].setEasingType(EASE_CIRCULAR_IN_OUT);
break;
caseSine:
servo[currentServo].setEasingType(EASE_SINE_IN_OUT);
break;
caseBack:
servo[currentServo].setEasingType(EASE_BACK_IN_OUT);
break;
caseElastic:
servo[currentServo].setEasingType(EASE_ELASTIC_IN_OUT);
break;
caseBounce:
servo[currentServo].setEasingType(EASE_BOUNCE_OUT);// No in prob as you want at end
break;
casePrecision:
servo[currentServo].setEasingType(EASE_PRECISION_OUT);// Meaining of precision?
break;
default:
printf("Invalid easing profile");
break;
}// switch
}
voidsetServoProfile_IN(EasingProfileEnum_tnewProfile){
switch(newProfile){
caseLinear:
servo[currentServo].setEasingType(EASE_LINEAR);// Is the same for IN_OUT
break;
caseQuadratic:
servo[currentServo].setEasingType(EASE_QUADRATIC_IN);
break;
caseCubic:
servo[currentServo].setEasingType(EASE_CUBIC_IN);
break;
caseQuartic:
servo[currentServo].setEasingType(EASE_QUARTIC_IN);
break;
caseCircular:
servo[currentServo].setEasingType(EASE_CIRCULAR_IN);
break;
caseSine:
servo[currentServo].setEasingType(EASE_SINE_IN);
break;
caseBack:
servo[currentServo].setEasingType(EASE_BACK_IN);
break;
caseElastic:
servo[currentServo].setEasingType(EASE_ELASTIC_IN);
break;
caseBounce:
servo[currentServo].setEasingType(EASE_BOUNCE_OUT);// No in prob as you want at end
break;
casePrecision:
servo[currentServo].setEasingType(EASE_PRECISION_OUT);// Meaining of precision?
break;
default:
printf("Invalid easing profile");
break;
}// switch
}
voidsetServoProfile_OUT(EasingProfileEnum_tnewProfile){
switch(newProfile){
caseLinear:
servo[currentServo].setEasingType(EASE_LINEAR);// Is the same for IN_OUT
break;
caseQuadratic:
servo[currentServo].setEasingType(EASE_QUADRATIC_OUT);
break;
caseCubic:
servo[currentServo].setEasingType(EASE_CUBIC_OUT);
break;
caseQuartic:
servo[currentServo].setEasingType(EASE_QUARTIC_OUT);
break;
caseCircular:
servo[currentServo].setEasingType(EASE_CIRCULAR_OUT);
break;
caseSine:
servo[currentServo].setEasingType(EASE_SINE_OUT);
break;
caseBack:
servo[currentServo].setEasingType(EASE_BACK_OUT);
break;
caseElastic:
servo[currentServo].setEasingType(EASE_ELASTIC_OUT);
break;
caseBounce:
servo[currentServo].setEasingType(EASE_BOUNCE_OUT);// No in prob as you want at end
break;
casePrecision:
servo[currentServo].setEasingType(EASE_PRECISION_OUT);// Meaining of precision?
break;
default:
printf("Invalid easing profile");
break;
}// switch
}
voidsetServoProfile_BOUNCING(EasingProfileEnum_tnewProfile){
switch(newProfile){
caseLinear:
servo[currentServo].setEasingType(EASE_LINEAR);// Is the same for _IN_OUT
break;
caseQuadratic:
servo[currentServo].setEasingType(EASE_QUADRATIC_BOUNCING);
break;
caseCubic:
servo[currentServo].setEasingType(EASE_CUBIC_BOUNCING);
break;
caseQuartic:
servo[currentServo].setEasingType(EASE_QUARTIC_BOUNCING);
break;
caseCircular:
servo[currentServo].setEasingType(EASE_CIRCULAR_BOUNCING);
break;
caseSine:
servo[currentServo].setEasingType(EASE_SINE_BOUNCING);
break;
caseBack:
servo[currentServo].setEasingType(EASE_BACK_BOUNCING);
break;
caseElastic:
servo[currentServo].setEasingType(EASE_ELASTIC_BOUNCING);
break;
caseBounce:
servo[currentServo].setEasingType(EASE_BOUNCE_OUT);// Replace _IN_OUT with _BOUNCING
break;
casePrecision:
servo[currentServo].setEasingType(EASE_PRECISION_OUT);// Meaning of precision?
break;
default:
printf("Invalid easing profile");
break;
}// switch
}
voidprintProfile(EasingProfileEnum_tprofile){
switch(profile){
caseLinear:
Serial.println("Linear");
break;
caseQuadratic:
Serial.println("Quadratic");
break;
caseCubic:
Serial.println("Cubic");
break;
caseQuartic:
Serial.println("Quartic");
break;
caseCircular:
Serial.println("Circular");
break;
caseSine:
Serial.println("Sine");
break;
caseBack:
Serial.println("Back");
break;
caseElastic:
Serial.println("Elastic");
break;
caseBounce:
Serial.println("Bounce");
break;
casePrecision:
Serial.println("Precision");
break;
caseINVALID_PROFILE:
Serial.println("INVALID_PROFILE");
break;
default:
Serial.println("Unknown");
break;
}
}
voidprintSuffix(intsuffix){
switch(suffix){
case0:
Serial.print("_In");
break;
case1:
Serial.println("_OUT");
break;
case2:
Serial.println("_IN_OUT");
break;
case3:
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.
How to get accurate DHT22 digital humidity sensor readings with an Arduino. Did you know it also measures temperature as Well? Find out why, in this page...
A PIR sensor lets your Arduino sense movement without contact. This tutorial covers PIR sensor basics, connecting one to an Arduino board and coding a motion detector.
Arduino Hall Effect Sensor: Add magnetic sensing superpowers to your Arduino projects with an easy-to-use hall effect sensor. With full code and layout...
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.