APIs, concepts, guides, and more
User Limits

Implement User Limits to configure and trigger actions based on specific input conditions in real-time.

πŸ”Ή What are User Limits?

User Limits are a functionality through which users can configure a comparison between inputs, so an output or an interrupt can be triggered when the comparison happens.

A User Limit will evaluate conditions set by the user. These conditions will be processed in the firmware of the RMP. When these conditions are met, the User Limit will generate an interrupt and can also output 32-bit word to any location in the MotionController firmware.

Each User Limit supports up to two input conditions and one output configuration block.

πŸ”Ή When to use User Limits?

User Limits are best suited for real-time logic processing that must happen in RMP firmware, not in the user application.

  1. When you want to generate an action based on a digital input state change.
  2. When you want to generate an action based on two different input state changes.
  3. When you want to generate an action based on a specific encoder/axis position.
  4. When you want to trigger a digital output when an Axis’ Actual Position is exceeded.

Where output actions are:

  • Axis ESTOP
  • Axis STOP
  • Axis ABORT
  • Digital Output State Change
  • Proportional Gain Change
  • Monitor Different Variables

πŸ”Ή Use cases

High-Speed Sorter Machine

The client has a high-speed conveyor machine that sorts and packages seed packets. They needed to increase the throughput of their machine but were limited by their PLC because the I/O latency was too high. High-speed sorting and packaging machines require high-speed and deterministic IO. Using User Limits, the client was able to set up a comparison of the conveyor position and the location of the packet that is monitored in controller firmware. As soon as the seed packet reached the diverter position, the User Limit comparison became true and fired the β€œextend” digital output within 1 EtherCAT sample (this system sample = 1 ms) to extend the appropriate actuator and divert the packet into its box.

πŸ”Ή RapidCode API Usage

Parameter Definition
int number Enter the user limit number that will be used. Starts from 0 and goes to (UserLimitCountGet – 1.
int conditionNumber Enter 0 or 1 to define the number of input conditions to be processed. Maximum of two input conditions can be combined with AND or OR logic. ConditionNumber would be set to 0 to compare one input bit. ConditionNumber would be set to 1 to compare two input bits.
RSIUserLimitLogic logic Represents logic that user will need to select to select how an input value is compared. See Enumerations below for valid options. Ex: user wants to compare an actual position to see when actual position is greater than 2000 counts. User would have to enter RSIUserLimitLogicGT
ulong address User needs to enter the address of the input. User can call Axis.AddressGet(), MotionController.AddressGet(), or MotionController.NetworkInputAddressGet() to get the address of a particular input.
int mask Decide the bits in an address which need to be used when comparing. Use 0xFFFFFFFF when all bits should be considered. (only applicable when the Condition’s DataType is a 32-bit integer or bitmask)
int limitValue The value to be compared which needs to be set here.
Warning
This method should be called first. UserLimitConditionSet() configures user limits to evaluate an input bit. It sets a condition for when the user limit should trigger.
To track more complex input bit addresses, user can get the address from VM3 or contact us.
Note
A user wants to compare the input condition of only a single axis position. If user intends on tracking two input conditions, call the function twice using 0 for the first condition and 1 for the 2nd condition.
If the user wants the User Limit to trigger when axis-0 position is greater than 2000 counts, then 2000 would be the limitValue.
For a Digital Input Slice I/O, if the 4th bit is changing then the user would simply enter: 0x00010000.

UserLimitConfigSet()

UserLimitConfigSet(int number, RSIUserLimitTriggerType triggerType, RSIAction action, int actionAxis, double duration)
Parameter Definition
int number Enter the user limit number that you are setting up. Starts from 0 and goes to (UserLimitCountGet – 1).
RSIUserLimitTriggerType triggerType Choose the how the condition<em>(s) should be evaluated.
RSIAction action Choose the action you want to cause when the User Limit triggers.
int actionAxis Select the axis that the action (defined above) will occur on.
double duration Enter the time delay (in seconds) before the action is executed after the User Limit has triggered. Resolution of the duration is sample periods (default is 1ms).
Warning
Call this method after UserLimitConditionSet(). This method configures a User Limit and enables it.

UserLimitOutputSet

UserLimitOutputSet(int number, int andMask, int orMask, ulong outputPtr, bool enabled)
Parameter Definition
int number Enter the user limit number that you want to configure to trigger an output. Starts from 0 and goes to (UserLimitCountGet – 1).
int andMask This is a 32-bit AND mask. andMask is always computed before orMask. Bit mask will be AND-ed with the data pointed by the ulong outputPtr.
int orMask This is a 32-bit OR mask. Bit mask will be OR-ed with the result of (andMask & outputPtr).
ulong outputPtr Pointer to any location in the motion controller firmware. This is a host address, like the values returned from AddressGet(...) methods.
bool enabled If TRUE, the output AND-ing and OR-ing will be executed when the User Limit triggers.
Warning
Call this method after UserLimitConfigSet(), but only if you want to configure an output to be triggered. The output will only be triggered when the User Limit is triggered.

πŸ”Ή Other User Limit Info

  • It is important to know that our API reserves a user limit per every axis.
  • The RMP controller has up to 2048 User Limit objects.
  • UserLimitCountSet should be done after either setting AxisCountSet and/or starting the network. (Internally, RapidCode Axis objects use UserLimits.) UserLimitCountSet must be called before creating an Axis or MultiAxis object with MotionController::AxisGet() or MotionController::MultiAxisGet().
  • User Limits are β€œzero” ordinate when accessing them.

πŸ”Ή FAQ’s

  1. Are User Limits monitored cyclically like PLC?
    Yes, User Limit objects are running inside the real-time motion firmware and they are cyclic (processed every cycle).

πŸ“œ Sample Code

Digital Input with 1 Condition

Trigger: Digital Input Conditions: 1

This sample code is done in AKD Drive with one Actual axis. There are a lots of available/free firmware address. Some are suggested in comment. Available/free firmware address can be found using vm3 as long as there is no label on address, it can be used.

  • C#

    /* This sample demonstrates how to configure a user limit triggered by a single digital input.
    When the input matches the specified value, the configured output is activated.
    Uses simulated IO with user buffer memory for demonstration purposes.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("πŸ“œ User Limit: Digital Input One Condition");
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // configure user limits
    controller.UserLimitCountSet(1); // set the amount of user limits to use
    controller.InterruptEnableSet(true); // enable user limit interrupts
    // create simulated io using user buffer memory
    ulong userBufferAddress = controller.AddressGet(RSIControllerAddressType.RSIControllerAddressTypeUSER_BUFFER, 0);
    IOPoint input0 = IOPoint.CreateDigitalInput(controller, userBufferAddress, bitNumber: 0);
    IOPoint output0 = IOPoint.CreateDigitalOutput(controller, userBufferAddress, bitNumber: 1);
    Helpers.CheckErrors(output0);
    // configure user limit condition
    controller.UserLimitConditionSet(
    number: 0,
    conditionNumber: 0,
    logic: RSIUserLimitLogic.RSIUserLimitLogicEQ,
    addressOfUInt32: input0.AddressGet(),
    userLimitMask: (uint)input0.MaskGet(),
    limitValueUInt32: (uint)input0.MaskGet());
    // configure user limit settings
    controller.UserLimitConfigSet(
    number: 0,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeSINGLE_CONDITION,
    action: RSIAction.RSIActionNONE,
    actionAxis: 0,
    duration: 0);
    // configure user limit output
    controller.UserLimitOutputSet(
    number: 0,
    andMask: (uint)output0.MaskGet(),
    orMask: (uint)output0.MaskGet(),
    outputPtr: output0.AddressGet(),
    enabled: true);
    Console.WriteLine("Waiting for input trigger...");
    // verify output is initially off
    if (output0.Get())
    throw new Exception("ERROR: Output should not be triggered yet");
    // set input high (bit 0) - condition met
    controller.MemorySet(input0.AddressGet(), 0b0001); // bit 0 is high
    // wait for user limit interrupt to confirm trigger
    if (controller.InterruptWait(1000) != RSIEventType.RSIEventTypeUSER_LIMIT)
    throw new Exception("ERROR: User limit did not trigger when input was high");
    // verify output was activated
    if (!output0.Get())
    throw new Exception("ERROR: Output should be high after user limit triggered");
    Console.WriteLine("βœ“ User limit triggered successfully - input high, output activated");
    // cleanup
    output0.Set(false);
    controller.UserLimitDisable(0);
    controller.UserLimitCountSet(0);
    Console.WriteLine("\nUser limit digital input one condition complete.");
    }
    // handle errors as needed
    finally
    {
    controller.Delete(); // dispose
    }
    static void CheckErrors(RapidCodeObject rsiObject)
    Checks for errors in the given RapidCodeObject and throws an exception if any non-warning errors are ...
    Definition _helpers.cs:15
    Helpers class provides static methods for common tasks in RMP applications.
    Definition _helpers.cs:5
    uint64_t AddressGet()
    Get the Host Address for the I/O point.
    void Set(bool state)
    Set the state of a Digital Output.
    static IOPoint * CreateDigitalInput(Axis *axis, RSIMotorDedicatedIn motorDedicatedInNumber)
    Create a Digital Input from an Axis' Dedicated Input bits.
    static IOPoint * CreateDigitalOutput(Axis *axis, RSIMotorDedicatedOut motorDedicatedOutNumber)
    Create a Digital Output from an Axis' Dedicated Output bits.
    bool Get()
    Get the state of Digital Input or Output.
    int32_t MaskGet()
    Get the bit mask for the I/O point.
    Represents one specific point: Digital Output, Digital Input, Analog Output, or Analog Input....
    Definition rsi.h:11550
    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
    RSIControllerAddressType
    Used to get firmware address used in User Limits, Recorders, etc.
    Definition rsienums.h:405
    RSIEventType
    Event Types or Status Bits.
    Definition rsienums.h:966
    RSIUserLimitLogic
    Logic options for User Limits.
    Definition rsienums.h:646
    RSIAction
    Action to perform on an Axis.
    Definition rsienums.h:1115
    RSIUserLimitTriggerType
    Trigger types for UserLimits.
    Definition rsienums.h:633

  • C++

    /* CONSTANTS */
    // *NOTICE* The following constants must be configured before attempting to run with hardware.
    // Axis configuration parameters
    const int AXIS_COUNT = 1; // Specify how many axes we will be using in this sample.
    const int AXIS_NUMBER = 0; // Specify which axis/motor we will be controlling.
    const double USER_UNITS = 1048576; // Specify your counts per unit / user units. (the motor used in this example has 1048576 encoder pulses per revolution)
    const int IO_NODE_NUMBER = 0; // which SqNode to use for I/O?
    const int USER_LIMIT = 0; // which user limit to use?
    const int CONDITION = 0; // which condition to use (0 or 1)
    const int INPUT_BIT_NUMBER = 0; // which input bit?
    // 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 */
    // Initialize MotionController class.
    // Setup the controller for the appropriate hardware configuration.
    if (USE_HARDWARE)
    {
    }
    else
    {
    std::cout << "This sample app cannot be run without hardware." << std::endl;
    return 0;
    }
    // RapidCode interface classes
    IO* io;
    IOPoint* digitalInput;
    try
    {
    // Create a new User Limit
    controller->UserLimitCountSet(1);
    // initialize the I/O class
    io = controller->IOGet(IO_NODE_NUMBER);
    digitalInput = IOPoint::CreateDigitalInput(io, INPUT_BIT_NUMBER);
    auto addr = digitalInput->AddressGet();
    auto mask1 = digitalInput->MaskGet();
    auto mask2 = digitalInput->MaskGet();
    // configure user limit to evaluate input bit
    controller->UserLimitConditionSet(USER_LIMIT,
    CONDITION,
    digitalInput->AddressGet(),
    digitalInput->MaskGet(),
    digitalInput->MaskGet());
    // enable the user limit, generate ESTOP_ABORT action when input is turned on
    controller->UserLimitConfigSet(USER_LIMIT, RSIUserLimitTriggerType::RSIUserLimitTriggerTypeSINGLE_CONDITION, RSIAction::RSIActionE_STOP_ABORT, AXIS_NUMBER, 0.0);
    printf("Waiting for the input bit to go high...\n");
    printf("\nPress Any Key To Exit.\n");
    // wait for user limit to trigger
    while (controller->OS->KeyGet((int32_t)RSIWait::RSIWaitPOLL) < 0)
    {
    printf("User Limit state is %d\r", controller->UserLimitStateGet(USER_LIMIT));
    controller->OS->Sleep(1);
    }
    // disable User Limit
    controller->UserLimitDisable(USER_LIMIT);
    }
    catch (RsiError const& rsiError)
    {
    printf("Text: %s\n", rsiError.text);
    return -1;
    }
    controller->Delete(); // Delete the controller as the program exits to ensure memory is deallocated in the correct order.
    return 0;

Digital Input with 2 Conditions

Trigger: Digital Input Conditions: 2

This sample code shows how to configure the RMP controller's User Limits to compare an two different input bits to a specific signal (high signal (1) OR low signal (0)). If the (2 conditions) patterns match, then the specified output bit is activated (turns high).
The INPUTS are specified in two different UserLimitConditionSet()
The OUTPUT is specified in UserLimitOutputSet()
In this example Beckhoff IO Terminals (Model EL1088 for Inputs and Model EL2008 for outputs) were used to control the Digital IO signals. Make sure to check the correct digital IO signal indexes of your system in:
RapidSetup -> Tools -> NetworkIO

  • C#

    /* This sample demonstrates how to configure a user limit triggered by two digital inputs.
    Uses AND logic - both inputs must be high for the output to activate.
    The RMP controller's User Limits compare two different input bits to specific values (high=1 or low=0).
    When both conditions match (using AND logic), the specified output bit is activated (turns high).
    In this example:
    - Two conditions are configured via UserLimitConditionSet() to monitor two separate inputs
    - Both inputs must turn high (1) to trigger the user limit
    - When triggered, UserLimitOutputSet() activates the output to high (1)
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("πŸ“œ User Limit: Digital Input Two Condition");
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // configure user limits
    controller.UserLimitCountSet(1); // set the amount of user limits to use
    controller.InterruptEnableSet(true); // enable user limit interrupts
    // create simulated io using user buffer memory
    ulong userBufferAddress = controller.AddressGet(RSIControllerAddressType.RSIControllerAddressTypeUSER_BUFFER, 0);
    IOPoint input0 = IOPoint.CreateDigitalInput(controller, userBufferAddress, bitNumber: 0);
    IOPoint input1 = IOPoint.CreateDigitalInput(controller, userBufferAddress, bitNumber: 1);
    IOPoint output0 = IOPoint.CreateDigitalOutput(controller, userBufferAddress, bitNumber: 2);
    Helpers.CheckErrors(output0);
    // configure first input condition (condition 0)
    controller.UserLimitConditionSet(
    number: 0,
    conditionNumber: 0,
    logic: RSIUserLimitLogic.RSIUserLimitLogicEQ,
    addressOfUInt32: input0.AddressGet(),
    userLimitMask: (uint)input0.MaskGet(),
    limitValueUInt32: (uint)input0.MaskGet());
    // configure second input condition (condition 1)
    controller.UserLimitConditionSet(
    number: 0,
    conditionNumber: 1,
    logic: RSIUserLimitLogic.RSIUserLimitLogicEQ,
    addressOfUInt32: input1.AddressGet(),
    userLimitMask: (uint)input1.MaskGet(),
    limitValueUInt32: (uint)input1.MaskGet());
    // configure user limit settings (AND logic for both conditions)
    controller.UserLimitConfigSet(
    number: 0,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeCONDITION_AND,
    action: RSIAction.RSIActionNONE,
    actionAxis: 0,
    duration: 0);
    // configure user limit output
    controller.UserLimitOutputSet(
    number: 0,
    andMask: (uint)output0.MaskGet(),
    orMask: (uint)output0.MaskGet(),
    outputPtr: output0.AddressGet(),
    enabled: true);
    Console.WriteLine("Waiting for both inputs to trigger...");
    // verify output is initially off
    if (output0.Get())
    throw new Exception("ERROR: Output should not be triggered yet");
    // set first input high (bit 0 only) - AND condition not met
    controller.MemorySet(input0.AddressGet(), 0b0001); // only bit 0 is high
    if (output0.Get())
    throw new Exception("ERROR: Output should not trigger with only one input");
    // set BOTH inputs high (bits 0 and 1) - AND condition met
    controller.MemorySet(input1.AddressGet(), 0b0011); // both bit 0 AND bit 1 are high
    // wait for user limit interrupt to confirm trigger
    if (controller.InterruptWait(1000) != RSIEventType.RSIEventTypeUSER_LIMIT)
    throw new Exception("ERROR: User limit did not trigger when both inputs were high");
    // verify output was activated
    if (!output0.Get())
    throw new Exception("ERROR: Output should be high after user limit triggered");
    Console.WriteLine("βœ“ User limit triggered successfully - both inputs high, output activated");
    // cleanup
    controller.UserLimitDisable(0);
    output0.Set(false);
    Console.WriteLine("\nUser limit digital input two condition complete.");
    }
    // handle errors as needed
    finally
    {
    controller.Delete(); // dispose
    }

E-Stop and Store Position

Trigger: Digital Input Conditions: 1 Event: EStop

This sample code shows how to configure a RMP controller's User Limit to compare an input bit to a specific signal (high signal (1) OR low signal (0)). If the (1 condition) pattern matches, then the specified input bit has been activated (turned high) and a User limit Event will trigger. In this example we configure a user limit to trigger when our INPUT turns high(1). Once the INPUT turns high(1) then our user limit will command an E-Stop action on the Axis and store the Axis Command Position. The INPUT is specified in UserLimitConditionSet()
The User Limit configuration is done on UserLimitConfigSet()
The specified address to record on User Limit Event is specified in UserLimitInterruptUserDataAddressSet()
The Data from the speficified addres is retrieved by calling InterruptUserDataGet()
In this example Beckhoff IO Terminals (Model EL1088 for Inputs) were used to control the Digital IO signals. Make sure to check the correct digital IO signal indexes of your system in:
RapidSetup -> Tools -> NetworkIO

  • C#

    /* This sample demonstrates how to configure a user limit that triggers an E-Stop when a digital input goes high.
    The axis command position is stored when the user limit triggers.
    Example uses Beckhoff IO Terminals (EL1088 for inputs).
    Check your digital IO signal indexes in RapidSetup -> Tools -> NetworkIO.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("πŸ“œ User Limit: EStop and Store Position");
    // set sample config params
    const int AXIS_INDEX = 0;
    const int INPUT_INDEX = 0;
    const int CONDITION = 0;
    const int USER_DATA_INDEX = 0;
    const double DURATION = 0.125; // time delay before action executes (seconds)
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // configure user limits
    controller.UserLimitCountSet(1);
    controller.InterruptEnableSet(true);
    // create simulated io using user buffer memory
    ulong userBufferAddress = controller.AddressGet(RSIControllerAddressType.RSIControllerAddressTypeUSER_BUFFER, 0);
    IOPoint input0 = IOPoint.CreateDigitalInput(controller, userBufferAddress, INPUT_INDEX);
    // get axis
    Axis axis = controller.AxisGet(Constants.AXIS_0_INDEX);
    // start velocity motion
    axis.AmpEnableSet(true);
    axis.MoveVelocity(velocity: 10.0, accel: 20.0);
    // configure user limit condition
    controller.UserLimitConditionSet(
    number: 0,
    conditionNumber: CONDITION,
    logic: RSIUserLimitLogic.RSIUserLimitLogicEQ,
    addressOfUInt32: input0.AddressGet(),
    userLimitMask: (uint)input0.MaskGet(),
    limitValueUInt32: 1);
    // configure user limit settings (e-stop action)
    controller.UserLimitConfigSet(
    number: 0,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeSINGLE_CONDITION,
    action: RSIAction.RSIActionE_STOP_ABORT,
    actionAxis: AXIS_INDEX,
    duration: DURATION);
    // configure user data to store axis command position
    controller.UserLimitInterruptUserDataAddressSet(
    number: 0,
    userDataIndex: USER_DATA_INDEX,
    address: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeCOMMAND_POSITION));
    Console.WriteLine("Axis moving... waiting for input trigger to E-Stop");
    // wait for user limit to trigger
    while (controller.InterruptWait(250) != RSIEventType.RSIEventTypeUSER_LIMIT)
    {
    // simulate input trigger
    controller.MemorySet(input0.AddressGet(), 1);
    }
    // wait for motion to stop
    // get the stored position data
    int triggeredUserLimit = controller.InterruptSourceNumberGet() - 1; // account for extra user limit per axis
    double interruptPosition = controller.InterruptUserDataDoubleGet(USER_DATA_INDEX);
    double positionInUserUnits = interruptPosition / axis.UserUnitsGet();
    Console.WriteLine($"βœ“ User limit triggered - E-Stop executed");
    Console.WriteLine($" Triggered User Limit: {triggeredUserLimit}");
    Console.WriteLine($" Position at trigger: {positionInUserUnits} (user units)");
    // cleanup
    axis.Abort();
    controller.UserLimitDisable(0);
    controller.UserLimitCountSet(0);
    Console.WriteLine("\nUser limit E-Stop store position 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
    static void PhantomAxisReset(Axis phantomAxis)
    Configures a phantom axis on the controller.
    Definition _helpers.cs:144
    uint64_t AddressGet(RSIAxisAddressType addressType)
    Get the an address for some location on the Axis.
    double UserUnitsGet()
    Get the number of counts per User Unit.
    void MoveVelocity(double velocity)
    Represents a single axis of motion control. This class provides an interface for commanding motion,...
    Definition rsi.h:5863
    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.
    RSIAxisAddressType
    Used to get firmware address used in User Limits, Recorders, etc.
    Definition rsienums.h:434

Change Feed Rate

Trigger: Position Conditions: 1 Event: FeedRate

Configure a UserLimit to change the FeedRate when the axis has reached a specified position.

  • C#

    /* This sample demonstrates how to configure a user limit to change the feed rate when the axis reaches a specified position.
    The user limit triggers when the command position exceeds the trigger value.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("πŸ“œ User Limit: Feed Rate Change at Position");
    // set sample config params
    const int CONDITION = 0;
    const double POSITION_TRIGGER_VALUE = 5.0;
    const double DEFAULT_FEED_RATE = 1.0;
    const double DESIRED_FEED_RATE = 2.0;
    const int DURATION = 0;
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // configure user limits
    controller.UserLimitCountSet(1);
    controller.InterruptEnableSet(true);
    // get axis
    Axis axis = controller.AxisGet(Constants.AXIS_0_INDEX);
    // set initial feed rate
    axis.FeedRateSet(DEFAULT_FEED_RATE);
    Console.WriteLine($"Initial Feed Rate: {axis.FeedRateGet()}");
    // configure user limit condition (trigger when position > POSITION_TRIGGER_VALUE)
    controller.UserLimitConditionSet(
    number: 0,
    conditionNumber: CONDITION,
    logic: RSIUserLimitLogic.RSIUserLimitLogicGT,
    addressOfDouble: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeCOMMAND_POSITION),
    limitValueDouble: POSITION_TRIGGER_VALUE);
    // configure user limit settings
    controller.UserLimitConfigSet(
    number: 0,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeSINGLE_CONDITION,
    action: RSIAction.RSIActionNONE,
    actionAxis: axis.NumberGet(),
    duration: DURATION);
    // configure user limit output to change feed rate
    controller.UserLimitOutputSet(
    number: 0,
    limitValueDouble: DESIRED_FEED_RATE,
    outputPtr: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeTARGET_FEEDRATE),
    enabled: true);
    Console.WriteLine($"Moving to position {POSITION_TRIGGER_VALUE + 1}...");
    Console.WriteLine($"Feed rate will change to {DESIRED_FEED_RATE} when position > {POSITION_TRIGGER_VALUE}");
    // start motion past the trigger position
    axis.AmpEnableSet(true);
    axis.MoveSCurve(POSITION_TRIGGER_VALUE + 1);
    // verify feed rate changed
    double finalFeedRate = axis.FeedRateGet();
    Console.WriteLine($"Final Feed Rate: {finalFeedRate}");
    if (finalFeedRate == DESIRED_FEED_RATE)
    Console.WriteLine("βœ“ User limit triggered - feed rate changed successfully");
    else
    Console.WriteLine($"βœ— Feed rate did not change (expected: {DESIRED_FEED_RATE}, actual: {finalFeedRate})");
    // cleanup
    controller.UserLimitDisable(0);
    controller.UserLimitCountSet(0);
    Console.WriteLine("\nUser limit feed rate change complete.");
    }
    // handle errors as needed
    finally
    {
    controller.Delete(); // dispose
    }
    static void AbortMotionObject(RapidCodeMotion motionObject)
    Aborts motion on the given RapidCodeMotion object (Axis or MultiAxis), waits for motion to stop,...
    Definition _helpers.cs:186
    void MoveSCurve(double position, double vel, double accel, double decel, double jerkPct)
    double FeedRateGet()
    Get the axis feed rate.
    void FeedRateSet(double rate)
    Set the feed rate for an Axis.
    int32_t NumberGet()
    Get the axis number.

Position Trigger

Trigger: Position Conditions: 1 Event: Abort

Configure a User Limit to call abort when a specified position is reached.

  • C#

    /* This sample demonstrates how to configure a user limit to abort motion when a specified position is reached.
    The user limit triggers when the command position exceeds the trigger value and executes an abort action.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("πŸ“œ User Limit: Position Abort");
    // set sample config params
    const int USER_LIMIT_NUMBER = 0;
    const double TRIGGER_POSITION = 5.0;
    const double MOVE_POSITION = 10.0; // must be past the trigger position
    const int OUTPUT_INDEX = 1;
    const int WAIT_FOR_TRIGGER_MILLISECONDS = 1000;
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // configure user limits
    controller.UserLimitCountSet(1);
    controller.InterruptEnableSet(true);
    // create simulated output
    ulong userBufferAddress = controller.AddressGet(RSIControllerAddressType.RSIControllerAddressTypeUSER_BUFFER, 0);
    IOPoint output0 = IOPoint.CreateDigitalOutput(controller, userBufferAddress, OUTPUT_INDEX);
    output0.Set(false); // start with output off
    // get axis
    Axis axis = controller.AxisGet(Constants.AXIS_0_INDEX);
    // configure user limit condition (trigger when position > TRIGGER_POSITION)
    controller.UserLimitConditionSet(
    number: USER_LIMIT_NUMBER,
    conditionNumber: 0,
    logic: RSIUserLimitLogic.RSIUserLimitLogicGT,
    addressOfDouble: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeCOMMAND_POSITION), // for real axis use actual position
    limitValueDouble: TRIGGER_POSITION);
    // configure user limit settings (abort action)
    controller.UserLimitConfigSet(
    number: USER_LIMIT_NUMBER,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeSINGLE_CONDITION,
    action: RSIAction.RSIActionABORT,
    actionAxis: axis.NumberGet(),
    duration: 0);
    // configure user limit output
    controller.UserLimitOutputSet(
    number: USER_LIMIT_NUMBER,
    andMask: (uint)output0.MaskGet(),
    orMask: (uint)output0.MaskGet(),
    outputPtr: output0.AddressGet(),
    enabled: true);
    Console.WriteLine($"Moving to position {MOVE_POSITION}...");
    Console.WriteLine($"User limit will trigger abort at position {TRIGGER_POSITION}");
    if (output0.Get())
    Console.WriteLine("ERROR: Output should not be triggered yet");
    // start motion
    axis.AmpEnableSet(true);
    axis.MoveSCurve(MOVE_POSITION);
    // wait for user limit interrupt
    RSIEventType interruptType = controller.InterruptWait(WAIT_FOR_TRIGGER_MILLISECONDS);
    if (interruptType == RSIEventType.RSIEventTypeUSER_LIMIT)
    {
    // note: object number for user_limit interrupts is unscaled, add axis count since there's one user limit per axis
    int interruptSource = controller.InterruptSourceNumberGet();
    int expectedSource = USER_LIMIT_NUMBER + 1; // account for extra user limit per axis
    if (interruptSource == expectedSource)
    {
    Console.WriteLine($"βœ“ User limit triggered - abort executed");
    Console.WriteLine($" Interrupt source: {interruptSource}");
    if (output0.Get())
    Console.WriteLine(" Output activated successfully");
    else
    Console.WriteLine(" WARNING: Output was not activated");
    }
    else
    {
    Console.WriteLine($"βœ— Got USER_LIMIT interrupt but wrong source (expected: {expectedSource}, actual: {interruptSource})");
    }
    }
    else
    {
    Console.WriteLine($"βœ— Expected USER_LIMIT interrupt but got {interruptType}");
    }
    // cleanup
    output0.Set(false);
    controller.UserLimitDisable(USER_LIMIT_NUMBER);
    controller.UserLimitCountSet(0);
    Console.WriteLine("\nUser limit position abort complete.");
    }
    // handle errors as needed
    finally
    {
    controller.Delete(); // dispose
    }

Set Position Triggered by User Limit

Custom

Configure two UserLimits. The first will trigger on a digital input and copy the Axis Actual Position to the UserBuffer and decelerate to zero velocity with TRIGGERED_MODIFY.
The second UserLimit will trigger after the first UserLimit triggers and the Axis gets to IDLE state and it will directly set the command position of an Axis from the position stored in the UserBuffer.

  • C#

    /* This sample demonstrates using two user limits in sequence.
    First user limit: triggers on digital input, executes triggered modify to decelerate, stores position.
    Second user limit: triggers when first completes and axis is idle, directly sets command position.
    This is an advanced example of coordinating multiple user limits.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("πŸ“œ User Limit: Command Position Direct Set");
    // set sample config params
    const int AXIS_COUNT = 1;
    const int USER_LIMIT_FIRST = 0;
    const int USER_LIMIT_SECOND = 1;
    const int USER_LIMIT_COUNT = 2;
    const int DURATION = 0;
    const bool ONE_SHOT = true; // user limit triggers only once
    // user limit interrupt constants
    const int COMMAND_POSITION_INDEX = 0;
    const int ACTUAL_POSITION_INDEX = 1;
    // get rmp objects
    try
    {
    Helpers.CheckErrors(controller);
    // configure user limits
    controller.AxisCountSet(AXIS_COUNT);
    controller.UserLimitCountSet(USER_LIMIT_COUNT);
    // get axis
    Axis axis = controller.AxisGet(Constants.AXIS_0_INDEX);
    // set the triggered modify values to stop very quickly
    /*
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ FIRST USER LIMIT β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    */
    // condition 0: trigger on digital input
    controller.UserLimitConditionSet(
    number: USER_LIMIT_FIRST,
    conditionNumber: 0,
    logic: RSIUserLimitLogic.RSIUserLimitLogicEQ,
    addressOfUInt32: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeDIGITAL_INPUTS),
    userLimitMask: 0x400000,
    limitValueUInt32: 0x400000);
    // configuration: cause a triggered_modify action on the axis
    controller.UserLimitConfigSet(
    number: USER_LIMIT_FIRST,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeSINGLE_CONDITION,
    action: RSIAction.RSIActionTRIGGERED_MODIFY,
    actionAxis: axis.NumberGet(),
    duration: DURATION,
    singleShot: ONE_SHOT);
    /*
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚ SECOND USER LIMIT β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    */
    // condition 0: wait for first user limit to trigger
    controller.UserLimitConditionSet(
    number: USER_LIMIT_SECOND,
    conditionNumber: 0,
    logic: RSIUserLimitLogic.RSIUserLimitLogicEQ,
    addressOfUInt32: controller.AddressGet(RSIControllerAddressType.RSIControllerAddressTypeUSERLIMIT_STATUS, USER_LIMIT_FIRST),
    userLimitMask: 1,
    limitValueUInt32: 1);
    // condition 1: AND wait for axis command velocity = 0.0
    controller.UserLimitConditionSet(
    number: USER_LIMIT_SECOND,
    conditionNumber: 1,
    logic: RSIUserLimitLogic.RSIUserLimitLogicEQ,
    addressOfDouble: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeCOMMAND_VELOCITY),
    limitValueDouble: 0.0);
    // configuration: no action, just monitoring
    controller.UserLimitConfigSet(
    number: USER_LIMIT_SECOND,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeCONDITION_AND,
    action: RSIAction.RSIActionNONE,
    actionAxis: 0,
    duration: 0,
    singleShot: ONE_SHOT);
    // start motion
    axis.ClearFaults();
    axis.AmpEnableSet(true);
    axis.MoveVelocity(velocity: 10, accel: 100);
    Console.WriteLine("Axis moving... waiting for user limit triggers");
    // configure and enable interrupts
    ConfigureUserLimitInterrupts(controller, axis, USER_LIMIT_FIRST);
    ConfigureUserLimitInterrupts(controller, axis, USER_LIMIT_SECOND);
    controller.InterruptEnableSet(true);
    // wait for and display interrupts
    WaitForInterrupts(controller);
    // cleanup
    axis.Abort();
    controller.UserLimitDisable(USER_LIMIT_FIRST);
    controller.UserLimitDisable(USER_LIMIT_SECOND);
    Console.WriteLine("\nUser limit command position direct set complete.");
    }
    // handle errors as needed
    finally
    {
    controller.Delete(); // dispose
    }
    // configure interrupt user data addresses for a user limit
    void ConfigureUserLimitInterrupts(MotionController controller, Axis axis, int userLimitIndex)
    {
    number: userLimitIndex,
    userDataIndex: COMMAND_POSITION_INDEX,
    address: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeCOMMAND_POSITION));
    controller.UserLimitInterruptUserDataAddressSet(
    number: userLimitIndex,
    userDataIndex: ACTUAL_POSITION_INDEX,
    address: axis.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeACTUAL_POSITION));
    }
    // wait for and display user limit interrupts
    void WaitForInterrupts(MotionController controller)
    {
    bool done = false;
    int timeout_milliseconds = 100;
    int interruptCount = 0;
    while (!done)
    {
    RSIEventType eventType = controller.InterruptWait(timeout_milliseconds);
    if (eventType != RSIEventType.RSIEventTypeTIMEOUT)
    {
    interruptCount++;
    Console.WriteLine($"\nInterrupt {interruptCount}: {eventType} at sample {controller.InterruptSampleTimeGet()}");
    }
    switch (eventType)
    {
    case RSIEventType.RSIEventTypeUSER_LIMIT:
    int userLimitNum = controller.InterruptSourceNumberGet();
    double cmdPos = controller.InterruptUserDataDoubleGet(COMMAND_POSITION_INDEX);
    double actPos = controller.InterruptUserDataDoubleGet(ACTUAL_POSITION_INDEX);
    Console.WriteLine($" UserLimit: {userLimitNum}");
    Console.WriteLine($" Command Position: {cmdPos}");
    Console.WriteLine($" Actual Position: {actPos}");
    break;
    case RSIEventType.RSIEventTypeTIMEOUT:
    done = true;
    Console.WriteLine($"\nβœ“ Completed - received {interruptCount} interrupts");
    break;
    default:
    break;
    }
    }
    }
    void TriggeredModifyDecelerationSet(double decel)
    Set the deceleration rate for an Triggered Modify Event.
    void TriggeredModifyJerkPercentSet(double jerkPct)
    Set the jerk percent for an Triggered Modify Event.
    void UserLimitInterruptUserDataAddressSet(int32_t number, uint32_t userDataIndex, uint64_t address)
    Set the User Data address based on a User Limit trigger.
    void ClearFaults()
    Clear all faults for an Axis or MultiAxis.

User Limit Triggered by Difference in Position of Two Axes

Trigger: Math Block Process Value Conditions: 1 Event: Abort


This sample code demonstrates how to use a MathBlock to calculate the difference of in position between two axes and trigger a UserLimit when the difference exceeds a certain value.

  • C#

    /* This sample app demonstrates configuring a MathBlock to calculate the difference of position between two axes
    and using that MathBlock process value in a UserLimit to trigger an abort action when the difference exceeds a threshold.
    This sample app requires at least 2 Axis so that positions can be read.
    */
    using RSI.RapidCode; // RSI.RapidCode.dotNET;
    Console.WriteLine("πŸ“œ MathBlock: Difference of Position UserLimit");
    // set sample config params
    const int INDEX_ZERO = 0;
    const RSIDataType DOUBLE = RSIDataType.RSIDataTypeDOUBLE;
    const RSIMathBlockOperation SUBTRACT = RSIMathBlockOperation.RSIMathBlockOperationSUBTRACT;
    // get rmp controller
    try
    {
    Helpers.CheckErrors(controller);
    // set mathblock & userlimit counts before any RapidCodeObject get/create other than the controller
    controller.MathBlockCountSet(1);
    controller.UserLimitCountSet(1);
    // get & configure both axes
    Axis axis0 = controller.AxisGet(Constants.AXIS_0_INDEX);
    Axis axis1 = controller.AxisGet(Constants.AXIS_1_INDEX);
    // set sample addresses
    ulong axis0PositionAddr = axis0.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeCOMMAND_POSITION);
    ulong axis1PositionAddr = axis1.AddressGet(RSIAxisAddressType.RSIAxisAddressTypeCOMMAND_POSITION);
    ulong mbProcessValueAddr = controller.AddressGet(RSIControllerAddressType.RSIControllerAddressTypeMATHBLOCK_PROCESS_VALUE, INDEX_ZERO);
    // MathBlock config & set (mb.value = axis0.position - axis1.position)
    var mbConfig = controller.MathBlockConfigGet(INDEX_ZERO);
    mbConfig.Operation = SUBTRACT;
    mbConfig.InputAddress0 = axis0PositionAddr; // input0: axis0 command position
    mbConfig.InputAddress1 = axis1PositionAddr; // input1: axis1 command position
    mbConfig.InputDataType0 = DOUBLE;
    mbConfig.InputDataType1 = DOUBLE;
    mbConfig.ProcessDataType = DOUBLE;
    controller.MathBlockConfigSet(INDEX_ZERO, mbConfig);
    // wait 1 sample for MathBlock config to take effect
    controller.SampleWait(1);
    // set UserLimit condition to trigger when absolute position difference exceeds threshold
    controller.UserLimitConditionSet(
    number: INDEX_ZERO,
    conditionNumber: 0,
    logic: RSIUserLimitLogic.RSIUserLimitLogicABS_GT,
    addressOfDouble: mbProcessValueAddr,
    limitValueDouble: 5 * Constants.AXIS_0_USER_UNITS); // 5 revolutions (values in firmware are not scaled by user units)
    // set UserLimit to abort when condition above is met
    controller.UserLimitConfigSet(
    number: INDEX_ZERO,
    triggerType: RSIUserLimitTriggerType.RSIUserLimitTriggerTypeSINGLE_CONDITION,
    action: RSIAction.RSIActionABORT,
    actionAxis: Constants.AXIS_0_INDEX,
    duration: 0);
    // move axis0 to trigger the UserLimit
    axis0.AmpEnableSet(true);
    axis0.MoveRelative(
    relativePosition: 10, // revolutions
    vel: 1,
    accel: 1,
    decel: 1,
    jerkPct: 0);
    axis0.MotionDoneWait();
    // get results
    var axis0State = axis0.StateGet();
    // print results
    Console.WriteLine($"Axis 0 state: {axis0State} (expected: {RSIState.RSIStateERROR})");
    // verify userlimit triggered abort action
    if (axis0State != RSIState.RSIStateERROR)
    throw new Exception("❌ UserLimit did not trigger correctly - both axes should be in error state.");
    // cleanup
    }
    // handle errors as needed
    finally
    {
    controller.UserLimitDisable(INDEX_ZERO); // disable user limit
    controller.Delete(); // dispose
    }
    const double AXIS_0_USER_UNITS
    Default: 1.
    Definition _constants.cs:17
    const int AXIS_1_INDEX
    Default: 1.
    Definition _constants.cs:12
    void MoveRelative(double relativePosition, double vel, double accel, double decel, double jerkPct)
    Command a relative point-to-point S-Curve motion.
    RSIState StateGet()
    Get the Axis or MultiAxis state.
    RSIMathBlockOperation
    MathBlock operations.
    Definition rsienums.h:1431
    RSIDataType
    Data types for User Limits and other triggers.
    Definition rsienums.h:659

  • C++

    /* CONSTANTS */
    // *NOTICE* The following constants must be configured before attempting to run with hardware.
    const int NUM_AXES = 2; // The number of axes to configure
    const int AXIS_X_INDEX = 0; // The index of the first axis
    const int AXIS_Y_INDEX = 1; // The index of the second axis
    // User Limit Configuration
    const double MAX_POSITION_DIFFERENCE = 0.5; // the maximum position difference before the user limit triggers
    const RSIAction USER_LIMIT_ACTION = RSIAction::RSIActionABORT; // the action to take when the user limit is triggered
    const int USER_LIMIT_DURATION = 0; // the time delay before the action is executed after the User Limit has triggered
    // Motion Parameters
    const double RELATIVE_POSITION = 2 * MAX_POSITION_DIFFERENCE; // the relative position to move the axes
    const double VELOCITY = 1; // the velocity to move the axes
    const double ACCELERATION = 10; // the acceleration of the axes movement
    const double DECELERATION = 10; // the deceleration of the axes movement
    const double JERK_PCT = 0; // the jerk percentage of the axes movement
    /* RAPIDCODE INITIALIZATION */
    // Create the controller
    // Variables to store initial object counts to restore later (starts at -1 to indicate it has not been set)
    int initialUserLimitCount = -1;
    int initialMathBlockCount = -1;
    // Set the exit code to an error value.
    int exitCode = -1;
    try // Ensure that the controller is deleted if an error occurs.
    {
    // Prepare the controller as defined in SampleAppsHelper.h depending on the configuration
    SampleAppsHelper::SetupController(controller, NUM_AXES);
    // Save initial object counts to restore later
    initialUserLimitCount = controller->UserLimitCountGet();
    initialMathBlockCount = controller->MathBlockCountGet();
    /* SAMPLE APP BODY */
    /* Configure the controller object counts */
    // Normally you would set the number of axes here, but for samples that is handled in the SampleAppsHelper::SetupController function
    // controller->AxisCountSet(NUM_AXES);
    // Each axis has a motion object associated with it, so we need to add one more motion object than axes
    const int multiAxisIndex = controller->AxisCountGet();
    controller->MotionCountSet(multiAxisIndex + 1);
    const int userLimitIndex = initialUserLimitCount;
    controller->UserLimitCountSet(initialUserLimitCount + 1);
    const int mathBlockIndex = initialMathBlockCount;
    controller->MathBlockCountSet(initialMathBlockCount + 1);
    // Get the axes
    Axis *axisX = controller->AxisGet(AXIS_X_INDEX);
    Axis *axisY = controller->AxisGet(AXIS_Y_INDEX);
    // Configure a multiaxis for the two axes
    MultiAxis *multiAxis = controller->MultiAxisGet(multiAxisIndex);
    multiAxis->AxisRemoveAll();
    multiAxis->AxisAdd(axisX);
    multiAxis->AxisAdd(axisY);
    multiAxis->Abort(); // make sure the multiaxis is not moving
    multiAxis->ClearFaults();
    // Read the configuration of the MathBlock
    MotionController::MathBlockConfig mathBlockConfig = controller->MathBlockConfigGet(mathBlockIndex);
    // Determine which axis address type to use based on the config USE_HARDWARE flag
    // Configure the MathBlock to subtract the position of the second axis from the position of the first axis
    mathBlockConfig.InputAddress0 = axisX->AddressGet(INPUT_AXIS_ADDRESS_TYPE);
    mathBlockConfig.InputAddress1 = axisY->AddressGet(INPUT_AXIS_ADDRESS_TYPE);
    // Set the MathBlock configuration
    controller->MathBlockConfigSet(mathBlockIndex, mathBlockConfig);
    // Wait a sample so we know the RMP is now processing the newly configured MathBlocks
    controller->SampleWait(1);
    std::cout << "MathBlock configured to subtract the position of the second axis from the position of the first axis." << std::endl;
    // Get the address of the MathBlock's ProcessValue to use in the UserLimit
    uint64_t mathBlockProcessValueAddress =
    // Configure the UserLimit to trigger when the absolute position difference is greater than MAX_POSITION_DIFFERENCE
    controller->UserLimitConditionSet(
    userLimitIndex, 0, RSIUserLimitLogic::RSIUserLimitLogicABS_GT, mathBlockProcessValueAddress, MAX_POSITION_DIFFERENCE
    );
    // Set the UserLimit action to abort motion (Note: since the axes are in a multiaxis, the other axis will also be aborted)
    controller->UserLimitConfigSet(
    userLimitIndex, RSIUserLimitTriggerType::RSIUserLimitTriggerTypeSINGLE_CONDITION, USER_LIMIT_ACTION, AXIS_X_INDEX,
    USER_LIMIT_DURATION
    );
    std::cout << "UserLimit configured to trigger when the absolute position difference is greater than " << MAX_POSITION_DIFFERENCE
    << " and abort motion." << std::endl;
    // Command motion to trigger the UserLimit
    std::cout << "Moving the axes to trigger the UserLimit..." << std::endl;
    axisX->AmpEnableSet(true); // Enable the motor.
    axisX->MoveRelative(RELATIVE_POSITION, VELOCITY, ACCELERATION, DECELERATION, JERK_PCT); // Move the axis to trigger the UserLimit.
    axisX->MotionDoneWait();
    // Disable the motor and the UserLimit
    axisX->AmpEnableSet(false); // Disable the motor.
    controller->UserLimitDisable(userLimitIndex);
    // The motion should have been aborted and both axes should be in an error state
    {
    std::cout << "Both axes are in the error state after the UserLimit triggered (This is the intended behavior)." << std::endl;
    exitCode = 0;
    }
    else
    {
    std::cout << "Error: The axes should be in an error state after the UserLimit triggers, but they are not." << std::endl;
    std::cout << "First Axis State: " << RSIStateMap.at(axisX->StateGet()) << std::endl;
    std::cout << "Second Axis State: " << RSIStateMap.at(axisY->StateGet()) << std::endl;
    exitCode = -1;
    }
    }
    catch (const std::exception &ex)
    {
    std::cerr << ex.what() << std::endl;
    exitCode = -1;
    }
    // Restore the object counts to the original values
    if (initialUserLimitCount != -1) { controller->UserLimitCountSet(initialUserLimitCount); }
    if (initialMathBlockCount != -1) { controller->MathBlockCountSet(initialMathBlockCount); }
    // Note: The axis and motion counts are handled by the SampleAppsHelper::Cleanup function
    // Clean up the controller and any other objects as needed
    // Delete the controller as the program exits to ensure memory is deallocated in the correct order
    controller->Delete();