How to fill the buffer fast using MoveSCurve

Hi,

For our application we’re currently using MoveSCurve() to append motion frames in the buffer. But in our application, the speed at which the machine executes these appended motion frames is faster than the speed at which we are able to fill these motion frames in the buffer. This results in an unpredictable jerky movement since the machine is hungry for motion frames and our application is not able to fill the buffer fast enough. We dont know why loading frames is taking so long.

But When we use MovePVT, we are able to get the desired speed at the machine end which means that MovePVT is appending motion frames faster than the speed at which the machine executes those frames. But unfortunately for our application, we have to use MoveSCurve purely because of the velocity curve it generates. We need to ramp up velocity via an S Curve, then maintain the specified velocity and then ramp down the velocity via an S curve.

When we use movePVT, we are using a large PVT array and calling movePVT once and that gives us the speed we need. But when we have to use MoveSCurve, we are appending one point every iteration.

Can you suggest a solution which will allow us to use MoveSCurve and also solve the speed issue?

Hi @RK_8323,

I understand that you are using MoveSCurve with the append attribute. Since windows isn’t realtime, I assumed the point you specify within your iteration is going to cover enough motion for your worst case windows latency. How long of an execution time are the points you are append within your loop? Can you confirm that the hungry for motion frames is just the axis reaching the end of the profile and undesirably stopping rather than throwing an error?

Would a solution where you are using MoveSCurve with a final velocity work for you? You could use it when you don’t expect change. Switching off append for the first delta velocity, switch on append for the desired ramp segments, switch back to a final velocity for holding stable. I don’t have a good sense for how frequently you are changing the velocity.

We have about 600,000 point moves with a point to point distance of about 0.05mm. So the distance to be traveled is really small but the time in which this has to be executed is a few milliseconds. I can confirm that the controller completes its motion and is waiting for the next points since those points were not added in the buffer fast enough.

Currently what we’re doing is that for every iteration our program checks if the buffer has enough space for a motion frame to be loaded. If it does find space, it loads the frame. If it doesn’t find space since the buffer is already full, it waits for the buffer to create some space and as soon as it sees that there is enough space, it loads another frame. That’s how the routine is programmed at the moment but this doesn’t run fast enough.

One question comes to our mind is that why does it work fast enough when we load the entire move in a single array and pass that array using MovePVT? Isn’t movePVT doing something similar to what we’re doing ?

Hi @RK_8323,

For your MoveSCurve application, you are using your program which is working within the non-realtime windows to decide when to update the realtime MotionController. It isn’t loading enough for the longest gaps in access to windows resources. While not true starvation of what to do next, it isn’t supplied enough data to continue as intended.

For MovePVT, we load as much data as you’ve provided to the library which provides it to the realtime up to the AxisBufferSize and continue to supply the realtime with more data that you’ve laid for us as needed in larger chunks that you are with the other application. Starvation is unlikely unless you are doing steaming motion and are failing providing enough new data before the existing buffer wears out.

Yes I understand. We’re trying to create the same functionality as you described for movePVT. But since a streaming option doesn’t exist where we can get the velocity profile like what we get for MoveScurve, we’re stuck with using point to point options (MoveSCurve) in our case which is causing the slow execution.

Our goal:

  • Velocity profile for every move should be as follows : Accelerate using an S Curve, maintain constant velocity and then decelerate using an S curve to reach the destination point.
  • Ensure that the buffer in the real time environment stays full so that we get the fast motion we desire.

When we use MovePVT , we’re getting the desired speed but the velocity profile is not what we’ve described earlier therefore we don’t want to use it. If we get a streaming motion option where the above two points are met, we can say that we’ve found our solution. We’ve explored movePT and it does give us the steady velocity but it does not create an S curve for its velocity while ramping up and ramping down. It achieves the desired steady state velocity instantly which generates jerky motion. If MovePT had an S curve ramping up and ramping down of velocity, our requirement would have been met.

Can we achieve this? Can you help us?

Hi @RK_8323,

I’ll set up a project to do some testing. Can you tell me why the following will not work for you?

  • MoveSCurve(position, vel, accel, decel, jerk, FinalVelocity); //Using your constant velocity as final velocity
  • MoveSCurve(destinationPoint, vel, accel, decel, jerk, 0);

More details about the specific distances/velocities/time windows would be helpful. Basically thing I should include for my own testing.

Hi @RK_8323,

I built up an app which does a sort of streaming MoveSCurve. The two main points for to avoid stalling are:

  • Use MotionHold for the initial buffer fill to ensure you don’t stall out by running out of appended moves.
  • Increase your AxisFrameBufferSize to ensure you have a sufficient buffer

Lets take a look at the images which demonstrate how increasing the AxisFrameBufferSize changed behavior.

Initial behavior:


You can see clear stalling as my python application wasn’t given enough appended MoveSCurve calls to keep it executing.

After increasing the AxisFrameBufferSize from 1024 to 16384, it looks like this:
image
No Stalling, just some jumps based on execution time of the frames created.

Motion hold is well documented on the website. Let me know if you have questions.

Another question is what you want things to look like in practice.
If I just use MoveSCurve without a final velocity, you’ll see velocity fluxations between each append:


And a notice little pause at each position:

I’m going to continue to work on this tomorrow to provide you with some data on streaming MoveSCurve with final velocities. I want to explore the cases where you are aren’t feeding it enough data relative to your consumption. I’ll follow up then.

Hey Jacob. Thank you so much for putting in the time. Can we connect over call to understand each other better? This is a bit tricky to follow.

Hi @RK_8323,

Yep. First let me get out some more information for you to digest for the discussion.

Here are the Traces that I would set up with RapidSetupX.

Below you an see an example of MoveSCurve with a final velocity. The red horizontal line at the top shows I maintain the existing velocity throughout all the motion segments without coming to rest between them.

Its important that you load motion segments long enough that you aren’t starving the buffer. The blue FrameIndex never catches up with the orange FrameLoadIndex.

To compare here is an example where I don’t provide enough points:

In it, you can see every time the blue FrameIndex catches up with the orange FrameLoadIndex that you get a notch in the red CommandVelocity and green CommandPosition based on the motion profile provided.


So things to consider:

  • You likely want to use a MotionHold to fill up the buffer as much as you can before starting the move.
  • You can increase the MotionController::AxisFrameBufferSizeSet to give yourself more time before a buffering deficit negatively impacts you.
  • Optional: You can reduce the FrameBuffer consumption with Axis::MotionAttributeMaskOnSet(RSIMotionAttrMaskNO_WAIT)
  • You’ll likely want to use MoveSCurve functions with finalvelocity to avoid coming to rest between each appended move.

The following is a python program I used for testing. I set it up for my test system. For anyone trying to use this, please make sure that the values are changed to match your hardware/system.

def main():
creation_params:RapidCode.CreationParameters = helpers.get_creation_parameters()
motionController:RapidCode.MotionController = RapidCode.MotionController.Create(creation_params)

helpers.check_errors(motionController)
print("MotionController creation error count: ", motionController.ErrorLogCountGet())

motionController.AxisCountSet(1)
# This needs to happen when creating objects. 
motionController.AxisFrameBufferSizeSet(0, 16384) # Set the frame buffer size to 16384

helpers.start_the_network(motionController)

axis:RapidCode.Axis = motionController.AxisGet(0)
helpers.check_errors(axis)
axis.UserUnitsSet(1048576) # 1 Rotation for my axis.
axis.ErrorLimitTriggerValueSet(1) #Overly large, but not focusing on error for this example.
axis.Abort() # Just to cover any unresolved motion from previous runs
axis.ClearFaults() # Clear the Abort
axis.AmpEnableSet(True, 100) # Ready the axis, Waiting up to 100ms to ensure the axis is fully enabled
axis.PositionSet(0) # Set the initial position to 0

# Set up a Motion Hold using a User Buffer
userBufferAddress = motionController.AddressGet(RapidCode.RSIControllerAddressType_RSIControllerAddressTypeUSER_BUFFER, 1)
userBufferTriggerValue = 0x1 # The value to trigger the user buffer
userBufferTriggerDisabled = 0x0 # The value to disable the user buffer
axis.MotionHoldTypeSet(RapidCode.RSIMotionHoldType_RSIMotionHoldTypeCUSTOM)
axis.MotionHoldUserAddressSet(userBufferAddress)
axis.MotionHoldUserMaskSet(userBufferTriggerValue)
axis.MotionHoldUserPatternSet(userBufferTriggerValue)
axis.MotionAttributeMaskOnSet(RapidCode.RSIMotionAttrMask_RSIMotionAttrMaskHOLD) # Set the hold gate attribute to true

# Ensure UserBuffer is set to 0 before starting the motion
if(motionController.MemoryGet(userBufferAddress) != userBufferTriggerDisabled):
    motionController.MemorySet(userBufferAddress, userBufferTriggerDisabled)

nearFullBuffer:int = axis.FrameBufferSizeGet() * 0.90 # Fill the buffer up to 99% full.  You want to avoid error messages from calling append too many times.
loadMoreBufferLevel:int = axis.FrameBufferSizeGet() * 0.8 # Just to keep it topped up.  Pick a different value that doesn't cause stalling if desired.

# Move a number of rotations
finalTargetPosition:float = 60.0 # The final position to move to, in user units

motionIncrement:float = 0.001 # The increment of the position to move each time

motionCalls:int = 0    
buffLoadPasses:int = 0
motionStarted:bool = False

position:float = 0.05 # Just a simple starting position
safeVelocity:float = 0.5 # The velocity to use for the motion
safeAcceleration:float = 10.0 # The acceleration to use for the motion
safeDeceleration:float = 10.0 # The deceleration to use for the motion
safeJerkPercent:float = 50.0 # The jerk to use for the motion

# Initial motion call of 1 rotation
axis.MoveSCurve(position, safeVelocity, safeAcceleration, safeDeceleration, safeJerkPercent, safeVelocity)
motionCalls += 1

axis.MotionAttributeMaskOffSet(RapidCode.RSIMotionAttrMask_RSIMotionAttrMaskHOLD) # Disable the hold gate attribute to allow motion to continue
axis.MotionAttributeMaskOnSet(RapidCode.RSIMotionAttrMask_RSIMotionAttrMaskAPPEND)
# Maybe enable this to reduce the frames produced by each MoveSCurve call.
# axis.MotionAttributeMaskOnSet(RapidCode.RSIMotionAttrMask_RSIMotionAttrMaskNO_WAIT) # Enable the append buffer attribute to allow motion to continue

# TODO - Change from position to end of profile logic
while not position >= finalTargetPosition:
    buffLoadPasses += 1 
    # Load the buffer to near full
    while axis.FramesToExecuteGet() < nearFullBuffer:
        position += motionIncrement #TODO - Here you load a profile position.  I'm just incrementing the position for this example.

        if position <= finalTargetPosition:
            axis.MoveSCurve(position, safeVelocity, safeAcceleration, safeDeceleration, safeJerkPercent, safeVelocity)
            motionCalls += 1
        else:
            print("Position limit reached, stopping motion calls.")
            break

        if( axis.FramesToExecuteGet() >= nearFullBuffer):
            break  # If the buffer is near full, wait for it to clear before continuing
    
    # Disable Hold if the buffer is near full
    if not motionStarted:
        motionController.MemorySet(userBufferAddress, userBufferTriggerValue) # Set the user buffer to start the motion
        motionStarted = True

    # Load more frames if the buffer is not full enough
    while axis.FramesToExecuteGet() > loadMoreBufferLevel:
        time.sleep(0.001)

# Be sure you aren't stuck with a non-zero final velocity after the profile is loaded.
axis.MoveVelocitySCurve(0, safeDeceleration, safeJerkPercent)

print("Total Reload Phases: ", buffLoadPasses, " Total Motion Calls: ", motionCalls)
axis.MotionDoneWait()
axis.Abort()
motionController.Delete()
1 Like