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:

\[Jerk\_Percent \;=\; \frac{t_{1} + t_{3}}{t_{1} + t_{2} + t_{3}} \times 100\% \]

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.

Maximum Acceleration

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

\[\displaystyle max\_accel \;=\; \frac{accel}{1 - \bigl(jerk\_percent \cdot 0.005\bigr)} \]

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

accel: the specified acceleration (or decel) used with MoveSCurve.

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

Note
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.

šŸ”¹ S-Curve Behavior for Short Moves

While S-Curve motion is designed for smooth, continuous profiles, its execution on a time-sampled system (such as the RMP's 1ms (default) servo sample) introduces quantization effects. This is most apparent during very short-duration moves. The firmware precisely tracks theoretical frame durations, even those with non-integer sample values, by accumulating time across servo samples. However, the commanded output is only updated once per sample, which can lead to the following distinct motion patterns:

1. Full S-Curve Profile (Moves >= 4 sample periods)

When commanded parameters result in a theoretical move time spanning several sample periods (typically 4 samples or more), the MoveSCurve function generates a near-ideal S-curve. The motion profile will consist of up at least four jerk phases: positive jerk (acceleration ramp-up), negative jerk (acceleration ramp-down to cruise or deceleration), negative jerk (deceleration ramp-up), and positive jerk (deceleration ramp-down). The result is a smooth change in acceleration, closely matching the theoretical profile.

2. Trapezoidal/Triangular Profile (Moves ā‰ˆ 3 sample periods)

As commanded parameters become more aggressive, the theoretical S-curve segments compress. If the total move time is short enough to be executed over just three sample periods, the profile will appear as a trapezoidal or triangular velocity profile. The firmware still calculates the S-curve with fractional time durations, but at the sample resolution, the continuous change in acceleration is effectively averaged out, resulting in a profile that presents as piecewise-constant acceleration.

3. Two-Sample Profile (Moves ā‰ˆ 2 sample periods)

Further increasing the aggressiveness can lead to a theoretical move time that executes over exactly two sample periods. In this scenario, the MoveSCurve function generates a profile that is distinctly triangular. The acceleration and deceleration phases each occur within a single sample period.

4. Single-Sample "Step" Move (Moves ā‰ˆ 1 sample period)

For extremely aggressive parameters where the theoretical move time is significantly less than two sample periods, the system cannot generate a multi-segment profile. Instead, it executes an instantaneous "step" move. The axis is commanded to the target position in the next servo sample, bypassing any profile generation. This is the fastest possible transition, limited only by the firmware's sample rate.

Key Takeaways

This quantization behavior highlights the practical limits of achieving perfectly smooth motion on a sampled control system.

  • For the smoothest motion, select parameters (velocity, acceleration, distance) that result in move times of at least 4 sample periods.
  • For very fast moves (< 4 sample periods), expect the velocity profile to degenerate from a pure S-curve into a trapezoidal, triangular, or single-step command.
  • Mechanical Considerations: Be aware that the high rates of acceleration and jerk in 2-sample and "step" moves can have significant mechanical implications for your system, and will rely on the tuning of your Axis to handle these transitions without overshoot or instability.
  • Verification: Use a tool like the RapidSetupX Scope to plot the commanded position and velocity to observe how quantization affects your specific motion profile.

šŸ“œ Sample Code

Basic SCurve Motion

  • C#

    axis.AmpEnableSet(true);
    axis.MoveSCurve(POSITION, VELOCITY, ACCELERATION, DECELERATION, JERK_PERCENT);
    axis.MotionDoneWait(); // wait for motion to complete

  • 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; // specify the start position
    const double POSITION_1 = 0.5; // specify the position to move to
    const double VELOCITY = 1; // specify your velocity - units: UserUnits/Sec
    const double ACCELERATION = 10; // specify your acceleration - units: UserUnits/Sec^2
    const double DECELERATION = 10; // specify your deceleration - units: UserUnits/Sec^2
    const double JERK_PERCENT = 50; // specify your jerk percent (0.0 to 100.0)
    const double FINAL_VELOCITY = 0.5; // specify your final velocity - units: UserUnits/Sec
    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#

    /* This sample demonstrates both trapezoidal and S-curve point-to-point motion profiles for multi-axis motion.
    The MultiAxis object coordinates the motion of multiple axes simultaneously.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("šŸ“œ MultiAxis Motion: Point to Point");
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // set the motion count to AxisCount + 1, every multiaxis needs a motion supervisor
    var axisCount = controller.AxisCountGet();
    controller.MotionCountSet(axisCount + 1);
    // get axes
    Axis axis0 = controller.AxisGet(Constants.AXIS_0_INDEX);
    Helpers.CheckErrors(axis0);
    Axis axis1 = controller.AxisGet(Constants.AXIS_1_INDEX);
    Helpers.CheckErrors(axis1);
    // get multiaxis (motion supervisor number is equal to number of axes because indexing starts at 0)
    MultiAxis multi = controller.MultiAxisGet(axisCount);
    Helpers.CheckErrors(multi);
    // remove any existing axes
    multi.AxisRemoveAll();
    // add axes to multiaxis
    multi.AxisAdd(axis0);
    multi.AxisAdd(axis1);
    // prepare for motion
    multi.Abort(); // stop any existing motion
    multi.ClearFaults();
    multi.AmpEnableSet(true);
    // disable position error for phantom axes
    axis0.ErrorLimitActionSet(RSIAction.RSIActionNONE);
    axis1.ErrorLimitActionSet(RSIAction.RSIActionNONE);
    // define motion parameters
    double[] positions1 = [ 5, 10 ]; // first set of positions
    double[] positions2 = [ 15, 15 ]; // second set of positions
    double[] velocities1 = [ 1000, 1000 ]; // velocities for first move
    double[] velocities2 = [ 1000, 1000 ]; // velocities for second move
    double[] accelerations = [ 500, 500 ]; // accelerations for both axes
    double[] decelerations = [ 500, 500 ]; // decelerations for both axes
    double[] jerkPercent = [ 50, 50 ]; // jerk percent for s-curve motion
    // move using s-curve motion profile
    Console.WriteLine($"\nMove 1 (S-Curve): Positions = [{positions1[0]}, {positions1[1]}]");
    multi.MoveSCurve(positions1, velocities1, accelerations, decelerations, jerkPercent);
    multi.MotionDoneWait(); // wait for motion to complete
    Console.WriteLine($"Axis 0 CommandPosition: {axis0.CommandPositionGet()} (expected: {positions1[0]})");
    Console.WriteLine($"Axis 1 CommandPosition: {axis1.CommandPositionGet()} (expected: {positions1[1]})");
    // move using trapezoidal motion profile
    Console.WriteLine($"\nMove 2 (Trapezoidal): Positions = [{positions2[0]}, {positions2[1]}]");
    multi.MoveTrapezoidal(positions2, velocities2, accelerations, decelerations);
    multi.MotionDoneWait(); // wait for motion to complete
    Console.WriteLine($"Axis 0 CommandPosition: {axis0.CommandPositionGet()} (expected: {positions2[0]})");
    Console.WriteLine($"Axis 1 CommandPosition: {axis1.CommandPositionGet()} (expected: {positions2[1]})");
    // cleanup
    Helpers.AbortMotionObject(multi);
    controller.MotionCountSet(axisCount); // remove multiaxis
    Console.WriteLine("\nMulti-axis point-to-point motion complete.");
    }
    // handle errors as needed
    finally
    {
    controller.Delete(); // dispose
    }
    Constants used in the C# sample apps.
    Definition _constants.cs:3
    const int AXIS_0_INDEX
    Default: 0.
    Definition _constants.cs:11
    const int AXIS_1_INDEX
    Default: 1.
    Definition _constants.cs:12
    void ErrorLimitActionSet(RSIAction action)
    Set the action that will occur when the Error Limit Event triggers.
    Represents a single axis of motion control. This class provides an interface for commanding motion,...
    Definition rsi.h:5863
    static MotionController * Get()
    Get an already running RMP EtherCAT controller.
    Represents the RMP soft motion controller. This class provides an interface to general controller con...
    Definition rsi.h:800
    void MoveTrapezoidal(const double *const position, const double *const vel, const double *const accel, const double *const decel)
    Point-to-point trapezoidal move.
    void AxisRemoveAll()
    Remove all axes from a MultiAxis group.s.
    void MoveSCurve(const double *const position, const double *const vel, const double *const accel, const double *const decel, const double *const jerkPct)
    Point-to-point S-Curve Move.
    void AxisAdd(Axis *axis)
    Add an Axis to a MultiAxis group.
    Represents multiple axes of motion control, allows you to map two or more Axis objects together for e...
    Definition rsi.h:10795
    void ClearFaults()
    Clear all faults for an Axis or MultiAxis.
    void Abort()
    Abort an axis.
    int32_t MotionDoneWait()
    Waits for a move to complete.
    int32_t AmpEnableSet(bool enable, int32_t ampActiveTimeoutMilliseconds=AmpEnableTimeoutMillisecondsDefault, bool overrideRestrictedState=false)
    Enable all amplifiers.
    RSIAction
    Action to perform on an Axis.
    Definition rsienums.h:1115
    Helpers namespace provides utility functions for common tasks in RMP applications.
    Definition helpers.h:21

  • 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
    // 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);
    Axis* axisY = controller->AxisGet(AXIS_Y);
    axisY->UserUnitsSet(USER_UNITS);
    axisY->PositionSet(0);
    // 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.
    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
    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.AmpEnableSet(true);
    // start motion
    axis.MoveSCurve(POSITION, VELOCITY, ACCELERATION, DECELERATION, JERK_PERCENT);
    // wait until position > 5
    while (axis.CommandPositionGet() < 5)
    Thread.Sleep(10);
    // modify motion to go 10x faster
    axis.MoveSCurve(POSITION, VELOCITY * 10, ACCELERATION, DECELERATION, JERK_PERCENT);
    // wait for motion done