Logistical Curve

This sketch contains a function softCurve which, given a value between 0 and 1 returns another value between 0 and 1 that has a "soft" start and end.

The logistical curve is infinitely differentiable, and so the speed, the acceleration, the jerk, and derivatives beyond that all have non-infinite values. The curve is, however, infinitely long. To accommodate that, this sketch clips the curve at -3 and 4. Some experimentation may be required, depending on your application.

#include <Servo.h>

Servo s;

const uint32_t SPEED = 1000;

const int target[] = {
  10, 60, 170, 90
};
const int targets = sizeof(target) / sizeof(*target);

uint32_t pollMs;
uint32_t startMs;
int whichTarget = 0;

void setup() {
  s.attach(6);
  pinMode(LED_BUILTIN, OUTPUT);
  s.write(target[0]);
  delay(SPEED);
  startMs = millis();
  digitalWrite(LED_BUILTIN, HIGH);
}


void loop() {
  if (millis() == pollMs) return;
  pollMs = millis();

  uint32_t ms = millis();

  if (ms - startMs >= SPEED) {
    digitalWrite(LED_BUILTIN, LOW);
    delay(SPEED);
    digitalWrite(LED_BUILTIN, HIGH);
    whichTarget = (whichTarget + 1) % targets;
    startMs = ms = millis();
  }

  const int a = target[whichTarget];
  const int b = target[(whichTarget + 1) % targets];

  s.write(
    softCurve((ms - startMs) / (double)SPEED) * (b - a) + a
  );
}

// SOFT CURVE implementation

#define LOGISTICAL(x) (exp(x)/(exp(x)+1))

double logistical(double x) {
  double y = exp(x);
  return y / (y + 1);
}

// pecomputed constants
// I make the start of the movement a little more jerky than the end
// making these values larger makes the start and end of the range of movement
// softer, at the price of the servo needing to move more quickly towards
// the center of its motion.

const double STARTING_X =  -3;
const double ENDING_X =  4;
const double STARTING_Y = LOGISTICAL(STARTING_X);
const double ENDING_Y = LOGISTICAL(ENDING_X);

/*
 * Compute a curve with a soft acelleration and decelleration, and less jerk
 * @param amt a value between 0 and 1, 0 being the start of the soft motion, 1 the end
 * @return a value between 0 and 1. Do arithmetic to get the range you want.
 */

double softCurve(double amt) {
  double x = STARTING_X + amt * (ENDING_X - STARTING_X);
  double y = logistical(x);

  // convert to a 0-1 value,
  y = (y - STARTING_Y) / (ENDING_Y - STARTING_Y);
  return y;
}


Share