The code at the bottom, section (C), rotates a servo to a target angle based on a user's serial input. It does so using millis() and the rotation is constant - the servo starts to rotate immediately, rotates fast or slow depending on a chosen value, and then comes to an abrupt halt.
I am not rotating a heavy camera or telescope, just a thin wooden pointer affixed to the servo horn.
I found Robert Penner's old GitHub archive that shows the below C++ implementation:
Quadratic ease out
float Quad::easeOut(float t,float b , float c, float d)
{
return -c *(t/=d)*(t-2) + b;
}
Cubic ease out
float Cubic::easeOut(float t,float b , float c, float d)
{
return c*((t=t/d-1)*t*t + 1) + b;
}
Then I found a rather more explanatory site, relating the above to JavaScript, claiming one can just as well forgo factors b and c to arrive at:
Cubic ease out
function cubicEaseOut(t, d)
{
return 1 - Math.pow(1 - (t / d), 3);
}
Still, how would I have to change the code below in part (C) to obtain "ease out" speed control, so the servo starts to rotate immediately but eventually comes to a "slow" or "smooth" or "very gentle" halt?
#include <Servo.h>
// Maximum rotation of 140° measured with protractor yielded 610ms = 0°
// 2090ms = 140°. Difference of 1480ms / 140° = 10.57ms per 1° rotation
const int msOffset = 610; // The servo's 0° rotation angle (microseconds)
const byte msAngleOneDegree = 10; // A 1° rotation (microseconds)
int msAnglePrevious;
int msAngleCurrent;
int msAngleTarget;
unsigned long timeNowServo = 0; // Timestamp that updates with each loop iteration (from Metro Mini's clock)
const byte timeServoInterval = 7; // Duration until next servo rotation
char inputBuffer[16]; // Store input from serial port
byte angleInput; // Store converted input (degrees)
Servo ovres;
void setup()
{
Serial.begin(115200);
randomSeed(analogRead(0));
ovres.attach(3);
ovres.write(msOffset);
msAnglePrevious = 610; // Seed with initial value to start
msAngleCurrent = 610; // Seed with initial value to start
}
void loop()
{
while (Serial.available() > 0)
{
Serial.readBytes(inputBuffer, sizeof(inputBuffer)); // Read from serial port into buffer
angleInput = atoi(inputBuffer); // Convert input string to a number
memset(inputBuffer, 0, sizeof(inputBuffer)); // Clears the buffer
}
rotateServo(angleInput);
}
void rotateServo(byte Angle)
{
/// (A) /// Basic rotate - but always at servo's max. speed
/*msAngleTarget = Angle * msAngleOneDegree + msOffset;
ovres.write(msAngleTarget);
delay(10);*/
/// (B) /// Basic rotate with for-loop and delay as speed control - but is blocking
/*msAngleTarget = Angle * msAngleOneDegree + msOffset;
if (msAngleTarget > msAnglePrevious)
{
for (int ms = msAnglePrevious; ms <= msAngleTarget; ms += msAngleOneDegree * 7)
{
ovres.write(ms);
delay(10);
}
}
if (msAngleTarget < msAnglePrevious)
{
for (int ms = msAnglePrevious; ms >= msAngleTarget; ms -= msAngleOneDegree * 3)
{
ovres.write(ms);
delay(10);
}
}
msAnglePrevious = msAngleTarget; // Store target as previous for next rotation*/
/// (C) /// Basic rotate with millis() - but does not "ease out" towards target angle
msAngleTarget = Angle * msAngleOneDegree + msOffset;
if (millis() - timeNowServo >= timeServoInterval)
{
timeNowServo = millis();
if (msAngleCurrent != msAngleTarget) // Don't write anything to the servo if target angle is reached
{
if (msAngleCurrent <= msAngleTarget)
{
msAngleCurrent += msAngleOneDegree * 4; // < slow or fast, the rotation is constant
ovres.write(msAngleCurrent);
}
else
{
if (msAngleCurrent >= msAngleTarget)
{
msAngleCurrent -= msAngleOneDegree * 2; // < slow or fast, the rotation is constant
ovres.write(msAngleCurrent);
}
}
}
}
}