Fun with millis(): Ramping Servo Movements

In this series I want to show short snippets of code around millis(). Here we will use millis() to control the movement of a RC servo with a ramp.  If you use a servo.write(90) to set a new position of the servo, the servo will move very fast to the new position. By using millis() we can not only slow down the movement but also let the servo start slow, increase the speed and decrease the speed at the end of the movement. This gives a smooth servo movement.

You could use delay to slow down the movement, but as the series is called "Fun with millis()" we will write a non blocking code based on millis(). Furthermore this method can be used to apply ramping servo movements to several servos.

The Servo Circuit

For this sketch we will need a servo connected to the Arduino and we add two buttons to start movements.

Slow Servo Arduino

A Ramping Servo Object

The Arduino "Servo.h" library offers a easy way to control servos. We will create our own servo class based on Servo.h and add some member function to gain a ramping movement.

/*
   move Servos with rampup/rampdown speed
   ramp done by time

   by noiasca
   2022-10-29 RampServo - https://wokwi.com/projects/346855291487257172 for https://forum.arduino.cc/t/problem-mit-der-servobewegung-bei-mobatools/1047250/11
   2022-09-12 SmoothServo https://wokwi.com/projects/342593181270934099  for  https://forum.arduino.cc/t/mehrere-servos-synchron-ansteuern/1030870/20
*/

#include <Servo.h>

constexpr uint8_t openPin {4};     // GPIO for servo movement
constexpr uint8_t closePin {3};    // GPIO for another servo movement
constexpr uint8_t servoAPin {8};   // GPIO for servo A

// make your own class of servos
class RampServo
{
  protected:
    const uint8_t startInterval {10};  // slow speed for start
    uint16_t target {90};              // target angle
    uint16_t current {90};             // current angle
    uint8_t interval {startInterval};  // delay time
    uint32_t previousMillis {0};       // last movement
    uint16_t minInterval {5};          // interval after rampUp/before ramp/down (=max Speed)
    uint8_t rampUp{30};                // angle used for ramp up
    uint8_t rampDown{10};              // angle used for ramp down
    uint16_t angleStart{90};           // what was the start angle

  public:
    Servo servo;

    void begin(const byte pin)
    {
      servo.attach(pin);
      servo.write(target);   // bring the servo to a defined angle
    }

    void set(uint16_t target, uint16_t minInterval = 1)
    {
      this->target = target;
      this->minInterval = minInterval;
      angleStart = current;
      interval = startInterval;
    }

    //      ____
    // ____/    \____
    //     u    d
    void update(uint32_t currentMillis = millis())
    {
      if (currentMillis - previousMillis > interval && current != target)  // slow down the servos
      {
        previousMillis = currentMillis;
        int16_t total = angleStart - target;   // total way
        int16_t remaining = current - target;  // rest way
        total = abs(total);
        remaining = abs(remaining);
        if (remaining < rampDown)
        {
          //Serial.println(F("ramp Down"));
          if (interval < startInterval) interval++;  //rampDown --> geschwidigkeit vermindern / interval erhöhen
        }
        else if (remaining > rampUp)
        {
          //Serial.println(F("ramp Up"));
          if (interval > minInterval) interval--;  //rampUp --> geschwindigkeit erhöhen/interval reduzieren
        }
        //Serial.print(F(" interval=")); Serial.println(interval);
        if (target < current)
        {
          current--;
        }
        else if (target > current)
        {
          current++;
        }
        servo.write(current);
      }
    }
};

RampServo servoA;  // create a servo object


void setup()
{
  Serial.begin(115200);
  servoA.begin(servoAPin);          // start the servo object

  pinMode(openPin, INPUT_PULLUP);
  pinMode(closePin, INPUT_PULLUP);
}

void doorOpen()
{
  Serial.println(F("move open"));
  servoA.set(0);     // new angle

  //servoA.servo.write(90); // hardcoded write
}

void doorClose()
{
  Serial.println(F("move close"));
  servoA.set(180);

}

void loop()
{
  // read buttons, sensors...
  if (digitalRead(openPin) == LOW) doorOpen();
  if (digitalRead(closePin) == LOW) doorClose();

  // call all servos
  uint32_t currentMillis = millis();
  servoA.update(currentMillis);  // call the update method in loop
 
}

The member function void begin(byte pin)

The member function begin() takes one parameter: the pin where we will connect the servo pin to. This function replaces the attach method of the Servo class. Call the begin function in your setup.

The member function void setSpeed(uint8_t newSpeed)

With this member function you can influence the speed of the servo movement. Higher values will result in a longer "delay" between each move.

The member function void set(uint16_t newTarget, uint16_t minInterval = 1)

This member function replaces the "servo.write(newAngle)". It sets the new target value for the servo angle. Optionally you can decrease the speed of the movement by setting a higher interval.

The member function void update()

update() is the run method. It will adjust the current angle in the proper interval until the target angle is reached. Call this member function for all your servo objects in your loop().

The movement of the Servo is splitted in 3 phases:

  • the rampup
  • the fast movement
  • the ramp down

Ramping of a Servo with Arduino

The movement starts with a slow speed and increases the speed during ramp up. If the speed reaches the final speed, the movement will continue until the servo comes close to the end of the target angle. The movement will be slowed down (=ramp down).

It the movement is shorter than ramp up and ramp down the ramp up will be shortened so that the ramp down can be executed to ensure a smooth movement.

Servo with ramp of speed with Arduino

Summary

You see, slowing down actions on the Arduino is quite easy using millis(). There is no need to block the code with a delay().

Links

(*) Disclosure: Some of the links above are affiliate links, meaning, at no additional cost to you I will earn a (little) comission if you click through and make a purchase. I only recommend products I own myself and I'm convinced they are useful for other makers.

History

First upload: 2022-11-01 | Version: 2024-03-22