APIs, concepts, guides, and more
S-Curve Motion

S-Curve Motion offers a smoother acceleration profile compared to Trapezoidal Motion, utilizing a "Jerk Percent" parameter to fine-tune how acceleration phases are shaped, allowing precise control over motion transitions and peak accelerations.

šŸ”¹ What is S-Curve Motion?

S-Curve Motion initiates a point-to-point motion using an ā€œSā€ shaped velocity profile similar to that shown below.

This move type generates a motion that takes the same time as a trapezoidal move but allows the acceleration to have smoother transitions.

This comes at the expense of requiring larger peak accelerations and decelerations. The total move time is always the same as the equivalent trapezoidal move.

Note
You can change the velocity, acceleration, and deceleration of a MoveSCurve that is executing on the fly simply by calling the function again with different parameters.

šŸ”¹ S-Curve Motion Overview

Jerk % Notes Acceleration Velocity
0%

Acceleration is always constant. This is the same as a Trapezoidal move profile.

Peak Acceleration is specified by the user.

Image Image
50%

Acceleration is ramping 50% of the time and constant for 50% of the time.

Peak Acceleration is 133% of the value specified by the user.

Image Image
100%

Acceleration is ramping 100% of the time and is never constant.

Peak Acceleration is 200% of the value specified by the user.

Image Image

Image

šŸ”¹ What is Jerk Percent?

Jerk percent is the percentage of time spent with non-zero jerk during the acceleration and deceleration segments of moves.

Image

For the above diagram, the jerk percent is calculated as:

Image

If the jerk percent for a move is zero, then it imitates a trapezoidal move.

For moves that use jerk percent, the acceleration and deceleration arguments represent the average acceleration and deceleration values for the move, not the peak values. As a consequence, the time of the move remains independent of the jerk percent value, which allows users to more easily calculate the time of a move. For point-to-point moves, this time is the same as a trapezoidal move with the same move parameters.

In many applications it is important to know the maximum acceleration and deceleration of a particular move. As discussed earlier, when using jerk percent, the average acceleration and decelerations are specified (not the maximum values). Fortunately, there is a simple conversion between average and maximum accelerations:

Image

Maximum Acceleration

Changing the Jerk Percent will change the max acceleration given by this formula:

Image

Where,

max_accel: the maximum acceleration (or decel) for a point-to-point profile.

accel: the specified acceleration (or decel) from the application code.

jerk_percent: the specified jerk percentage (0 to 100.0) from the application code.

šŸ”¹ Multi-Axis S-Curve Motion Overview

All Point to Point methods can be used for coordinated motion on multiaxis objects. It is important that you set up your arrays appropriately for a multiaxis object. See the examples below on how to structure your arrays for Multi-Axis S-Curve Motion.

EXAMPLE 1 - Three Axis Multi-Axis XYZ with MoveSCurve()

The position data for all three axes are combined into the array. The time array is shared for all axes. This is why you need 3x as many position points than you need time points. See the example below:

Axis1 ā†’ relativePosition[0] = 10; velocity[0] = 10; acceleration[0] = 100; deceleration[0] = 100; jerkPCT[0] = 50;\ Axis2 ā†’ relativePosition[1] = 10; velocity[1] = 10; acceleration[1] = 100; deceleration[1] = 100; jerkPCT[0] = 50;\ Axis3 ā†’ relativePosition[2] = 10; velocity[2] = 10; acceleration[2] = 100; deceleration[2] = 100; jerkPCT[0] = 50;

multiAxis.MoveSCurve(relativePosition, velocity, acceleration, deceleration, jerkPCT);

Note
The jerkPCT in the example above is a double-precision percent value and NOT a double-precision decimal value.
The values passed to the function MoveSCurve() will be multiplied internally by the specified user units (AKA: counts per unit). The reason for this is to avoid creating an extra input array. We simply just use the same array you have passed to the function. Therefore, the values passed to the function might not be the same ones once the function has been triggered.

šŸ“œ Sample Code

Basic SCurve Motion

  • C#

    public const double POSITION = 2; // Specify the position to travel to.
    public const double VELOCITY = 200; // Specify your velocity. - units: UserUnits/Sec
    public const double ACCELERATION = 100; // Specify your acceleration. - units: UserUnits/Sec^2
    public const double DECELERATION = 100; // Specify your deceleration. - units: UserUnits/Sec^2
    public const double JERK_PERCENT = 50; // Specify your jerk percent (0.0 to 100.0)
    axis.MoveSCurve(Constants.POSITION); // Command SCurve Motion. This overload willuse default velocity, acceleration, deceleration, jerk values for the axis. See axis config to learn how to set those values.
    axis.MotionDoneWait(); // Wait for motion to finish

  • C++

    // *NOTICE* The following constants must be configured before attempting to run with hardware.
    const int NUM_AXES = 1; // The number of axes to configure
    const int AXIS_INDEX = 0; // The index of the axis to configure
    // Motion Parameters
    const double POSITION_0 = 0;
    const double POSITION_1 = 0.5;
    const double VELOCITY = 1;
    const double ACCELERATION = 10;
    const double DECELERATION = 10;
    const double JERK_PERCENT = 50;
    const double FINAL_VELOCITY = 0.5;
    std::cout << "SCurve Motion:" << std::endl;
    std::cout << "Moving to position: " << POSITION_1 << std::endl;
    axis->MoveSCurve(POSITION_1);
    axis->MotionDoneWait(TIMEOUT);
    std::cout << "Motion Complete" << std::endl;
    std::cout << "Moving back to position: " << POSITION_0 << std::endl;
    axis->MoveSCurve(POSITION_0);
    axis->MotionDoneWait(TIMEOUT);
    std::cout << "Motion Complete\n" << std::endl;

Multi-Axis SCurve Motion

  • C#

    // Constants
    const int NUM_OF_AXES = 2; // Specify the number of axes (Make sure your axis count in RapidSetup is 2!)
    // Parameters
    double[] positions1 = new double[NUM_OF_AXES] { 5, 10 }; // The first set of positions to be moved to
    double[] positions2 = new double[NUM_OF_AXES] { 15, 15 }; // The second set of positions to be moved to
    double[] velocities1 = new double[NUM_OF_AXES] { 1000, 1000 }; // The velocity for the two axes for the first move- Units: units/sec (driver will execute 10 rotations per second)
    double[] velocities2 = new double[NUM_OF_AXES] { 1000, 1000 }; // The velocity for the two axes for the second move
    double[] accelerations = new double[NUM_OF_AXES] { 500, 500 }; // The acceleration for the two axes
    double[] decelerations = new double[NUM_OF_AXES] { 500, 500 }; // The deceleration for the two axes
    double[] jerkPercent = new double[NUM_OF_AXES] { 50, 50 }; // The jerk percent for the two axes
    controller.AxisCountSet(NUM_OF_AXES);
    controller.MotionCountSet(NUM_OF_AXES + 1);
    Axis axis0 = controller.AxisGet(Constants.X_AXIS_NUMBER); // Initialize axis0
    Axis axis1 = controller.AxisGet(Constants.Y_AXIS_NUMBER); // Initialize axis1
    HelperFunctions.CheckErrors(axis0); // [Helper Function] Check that 'axis0' has been initialized correctly
    HelperFunctions.CheckErrors(axis1); // [Helper Function] Check that 'axis1' has been initialized correctly
    // In this application, we have two Axis objects and one MultiAxis object, so three motion supervisors are required
    MultiAxis multi = controller.MultiAxisGet(NUM_OF_AXES); // Initialize a new MultiAxis object. MultiAxisGet takes a motion supervisor number as its argument.
    // This number is equal to the number of axes since motion supervisors are zero indexed (i.e., motion supervisors
    // 0 and 1 are used for axis0 and axis1, so motion supervisor 2 is available for our MultiAxis object).
    HelperFunctions.CheckErrors(multi); // [Helper Function] Check that 'multi' has been initialized correctly
    controller.AxisCountSet(NUM_OF_AXES); // Set the number of axis being used. A phantom axis will be created if for any axis not on the network. You may need to refresh rapid setup to see the phantom axis.
    multi.AxisRemoveAll(); // Remove all current axis if any. So we can add new ones
    multi.AxisAdd(axis0); // Add axis0 to the MultiAxis object
    multi.AxisAdd(axis1); // Add axis1 to the MultiAxis object
    multi.Abort(); // If there is any motion happening, abort it
    multi.ClearFaults(); // Clear any faults
    multi.AmpEnableSet(true); // Enable the motor
    axis0.ErrorLimitActionSet(RSIAction.RSIActionNONE); // Disable poistion error for Phantom Axes.
    axis1.ErrorLimitActionSet(RSIAction.RSIActionNONE); // Disable poistion error for Phantom Axes.
    multi.MoveSCurve(positions1, velocities1, accelerations, decelerations, jerkPercent); // Move to the positions specified in positions1 using a trapezoidal motion profile
    multi.MotionDoneWait(); // Wait for motion to finish
    Assert.That(axis0.CommandPositionGet(), Is.EqualTo(positions1[0]), "The first axis in the multi axis object should be commanded to move to the firt element of the array");
    Assert.That(axis1.CommandPositionGet(), Is.EqualTo(positions1[1]), "The second axis in the multi axis object should be commanded to move to the second element of the array");
    multi.MoveTrapezoidal(positions2, velocities2, accelerations, decelerations); // Move to the positions specified in positions2 using a SCurve motion profile
    multi.MotionDoneWait(); // Wait for the motion to finish
    Assert.That(axis0.CommandPositionGet(), Is.EqualTo(positions2[0]), "The first axis in the multi axis object should be commanded to move to the firt element of the array");
    Assert.That(axis1.CommandPositionGet(), Is.EqualTo(positions2[1]), "The second axis in the multi axis object should be commanded to move to the second element of the array");
    multi.AmpEnableSet(false); // Disable the axes

  • C++

    /* CONSTANTS */
    // *NOTICE* The following constants must be configured before attempting to run with hardware.
    // Axis configuration parameters
    const int AXIS_COUNT = 2;
    const int AXIS_X = 0;
    const int AXIS_Y = 1;
    const int MOTION_COUNT = 1;
    const double USER_UNITS = 1048576;
    // Motion parameters
    const double START_POSITION[AXIS_COUNT] = { 0, 0 };
    const double END_POSITION[AXIS_COUNT] = { 1, 2 };
    const double VELOCITY[AXIS_COUNT] = { 1, 2 };
    const double ACCELERATION[AXIS_COUNT] = { 10, 20 };
    const double DECELERATION[AXIS_COUNT] = { 10, 20 };
    const double JERK_PERCENTAGE[AXIS_COUNT] = { 0, 0 };
    // To run with hardware, set the USE_HARDWARE flag to true AFTER you have configured the parameters above and taken proper safety precautions.
    USE_HARDWARE = false;
    /* SAMPLE APP BODY */
    // Create and initialize RsiController class
    MotionController* controller = MotionController::CreateFromSoftware();
    // Setup the controller for the appropriate hardware configuration.
    if (USE_HARDWARE)
    {
    }
    else
    {
    SampleAppsHelper::SetupControllerForPhantoms(controller, AXIS_COUNT, { AXIS_X, AXIS_Y });
    }
    try
    {
    // enable one MotionSupervisor for the MultiAxis
    controller->MotionCountSet(controller->AxisCountGet() + 1);
    // Get Axis X and Y respectively.
    Axis* axisX = controller->AxisGet(AXIS_X);
    axisX->UserUnitsSet(USER_UNITS);
    axisX->PositionSet(0);
    axisX->ErrorLimitActionSet(RSIAction::RSIActionNONE);
    Axis* axisY = controller->AxisGet(AXIS_Y);
    axisY->UserUnitsSet(USER_UNITS);
    axisY->PositionSet(0);
    axisY->ErrorLimitActionSet(RSIAction::RSIActionNONE);
    // Initialize a MultiAxis, using the last MotionSupervisor.
    MultiAxis* multiAxisXY = controller->MultiAxisGet(controller->MotionCountGet() - 1);
    multiAxisXY->AxisAdd(axisX);
    multiAxisXY->AxisAdd(axisY);
    // make sure all axes are enabled and ready
    multiAxisXY->Abort();
    multiAxisXY->ClearFaults();
    multiAxisXY->AmpEnableSet(true);
    // Set SYNC_START motion attribute mask.
    multiAxisXY->MotionAttributeMaskOnSet(RSIMotionAttrMask::RSIMotionAttrMaskSYNC_START);
    printf("\nMotionStart...");
    // Commanding motion using Syncpptart to start motion for both the axis at the same time.
    multiAxisXY->MoveSCurve(END_POSITION, VELOCITY, ACCELERATION, DECELERATION, JERK_PERCENTAGE);
    multiAxisXY->MotionDoneWait();
    // Calling function created on top.
    PrintResult(axisX, axisY);
    // Set SYNC_END motion attribute mask
    multiAxisXY->MotionAttributeMaskOnSet(RSIMotionAttrMask::RSIMotionAttrMaskSYNC_END);
    printf("\nMotionStart...");
    // Commanding motion using SyncEnd to end motion for both the axis at the same time.
    multiAxisXY->MoveSCurve(START_POSITION, VELOCITY, ACCELERATION, DECELERATION, JERK_PERCENTAGE);
    multiAxisXY->MotionDoneWait();
    // Calling function created on top
    PrintResult(axisX, axisY);
    printf("\nTrapezoidal Motion Done\n");
    // Disable the all the axes
    multiAxisXY->AmpEnableSet(false);
    }
    catch (RsiError const& err)
    {
    printf("\n%s\n", err.text);
    return -1;
    }
    controller->Delete();
    return 0;

Motion: Modify

This sample application demonstrates how to use change the velocity parameter of a motion that is partially complete.

  • C#

    axis.MoveSCurve(20, Constants.VELOCITY, Constants.ACCELERATION, Constants.DECELERATION, Constants.JERK_PERCENT);
    if (axis.CommandPositionGet() > 2)
    {
    // Go 10X faster
    axis.MoveSCurve(20, Constants.VELOCITY * 10, Constants.ACCELERATION, Constants.DECELERATION, Constants.JERK_PERCENT);
    }