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.
- When you want to generate an action based on a digital input state change.
- When you want to generate an action based on two different input state changes.
- When you want to generate an action based on a specific encoder/axis position.
- 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
- 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#
const int INPUT_INDEX = 0;
const int OUTPUT_INDEX = 1;
int userLimitNumber = 0;
int condition = 0;
uint test = (uint)input0.
MaskGet();
uint inputMask = (uint)input0.
MaskGet();
uint limtValue = (uint)input0.
MaskGet();
condition,
logic,
inputAddress,
inputMask,
limtValue);
int axisNumber = 0;
int duration = 0;
triggerType,
action,
axisNumber,
duration);
uint andMask = (uint)output0.
MaskGet();
uint orMask = (uint)output0.
MaskGet();
bool enableOutput = true;
andMask,
orMask,
outputAddress,
enableOutput);
{
Assert.That(output0.
Get(), Is.False,
"We expect the output to NOT be triggered");
}
Assert.That(output0.
Get(), Is.True,
"We expect the output to be triggered");
- C++
const int AXIS_COUNT = 1;
const int AXIS_NUMBER = 0;
const double USER_UNITS = 1048576;
const int IO_NODE_NUMBER = 0;
const int USER_LIMIT = 0;
const int CONDITION = 0;
const int INPUT_BIT_NUMBER = 0;
USE_HARDWARE = false;
if (USE_HARDWARE)
{
}
else
{
std::cout << "This sample app cannot be run without hardware." << std::endl;
return 0;
}
IO* io;
try
{
io = controller->
IOGet(IO_NODE_NUMBER);
digitalInput = IOPoint::CreateDigitalInput(io, INPUT_BIT_NUMBER);
auto mask1 = digitalInput->
MaskGet();
auto mask2 = digitalInput->
MaskGet();
CONDITION,
RSIUserLimitLogic::RSIUserLimitLogicEQ,
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");
while (controller->
OS->
KeyGet((int32_t)RSIWait::RSIWaitPOLL) < 0)
{
}
}
{
printf("Text: %s\n", rsiError.text);
return -1;
}
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#
const int INPUT_INDEX0 = 0;
const int INPUT_INDEX1 = 1;
const int OUTPUT_INDEX = 2;
int userLimitNumber = 0;
int condition0 = 0;
uint input0Mask = (uint)input0.
MaskGet();
uint limitValue0 = (uint)input0.
MaskGet();
int condition1 = 1;
uint input1Mask = (uint)input1.
MaskGet();
uint limitValue1 = (uint)input1.
MaskGet();
condition0,
logic,
input0Address,
input0Mask,
limitValue0);
condition1,
logic,
input1Address,
input1Mask,
limitValue1);
int axis = 0;
int duration = 0;
triggerType,
action,
axis,
duration);
uint andMask = (uint)output0.
MaskGet();
uint orMask = (uint)output0.
MaskGet();
bool enableOutput = true;
andMask,
orMask,
outputAddress,
enableOutput);
{
Assert.That(output0.
Get(), Is.False,
"We expect the output to NOT be triggered");
Assert.That(output0.
Get(), Is.False,
"We expect the output to NOT be triggered");
Assert.That(output0.
Get(), Is.False,
"We expect the output to NOT be triggered");
}
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#
const int AXIS_INDEX = 0;
const int INPUT_INDEX = 0;
const int AXIS_COUNT = 1;
const int USER_LIMIT = 0;
const int CONDITION = 0;
const int LIMIT_VALUE = 1;
const double DURATION = 0.125;
const int USER_DATA_INDEX = 0;
axis = CreateAndReadyAxis(Constants.AXIS_NUMBER);
axis.MoveVelocity(10.0, 20.0);
USER_DATA_INDEX,
{
}
Console.WriteLine("TRIGGERED - User Limit Interrupt Position = " + interruptPosition / axis.UserUnitsGet());
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#
const int USER_LIMIT = 0;
const int USER_LIMIT_COUNT = 1;
const int AXIS_COUNT = 1;
const int CONDITION = 0;
const double POSITION_TRIGGER_VALUE = 5;
const int DURATION = 0;
const double DEFAULT_FEED_RATE = 1.0;
const double DESIRED_FEED_RATE = 2.0;
UInt64 feedRateAddress;
axis = CreateAndReadyAxis(Constants.AXIS_NUMBER);
axis.FeedRateSet(DEFAULT_FEED_RATE);
controller.
UserLimitConfigSet(USER_LIMIT, TRIGGER_TYPE, ACTION, axis.NumberGet(), DURATION);
Position Trigger
Trigger: Position
Conditions: 1
Event: Abort
Configure a User Limit to call abort when a specified position is reached.
- C#
const int AXIS_COUNT = 1;
const int USER_LIMIT_COUNT = 1;
const int USER_LIMIT_NUMBER = 0;
const double TRIGGER_POSITION = 0.05;
const double MOVE_POSITION = 1.0;
const int OUTPUT_INDEX = 1;
const int WAIT_FOR_TRIGGER_MILLISECONDS = 100;
axis = CreateAndReadyAxis(Constants.AXIS_NUMBER);
int condition = 0;
double limitValue = TRIGGER_POSITION;
condition,
limitValue);
int duration = 0;
triggerType,
action,
axis.NumberGet(),
duration);
uint andMask = (uint)output0.
MaskGet();
uint orMask = (uint)output0.
MaskGet(); ;
bool enableOutput = true;
andMask,
orMask,
outputAddress,
enableOutput);
Assert.That(output0.
Get(), Is.False,
"We expect the output to NOT be triggered");
axis.MoveSCurve(MOVE_POSITION);
{
Assert.That(controller.
InterruptSourceNumberGet(), Is.EqualTo(USER_LIMIT_NUMBER + AXIS_COUNT),
"We got a USER_LIMIT interrupt but it was the wrong one.");
Assert.That(output0.
Get(), Is.True,
"We expect the output to be turned on when the user limit triggers.");
}
else
{
Assert.Fail("We expected a USER_LIMIT interrupt when the trigger position was exceeded but instead got " + interruptType.ToString());
}
axis.AmpEnableSet(false);
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#
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;
const int COMMAND_POSITION_INDEX = 0;
const int ACTUAL_POSITION_INDEX = 1;
const int TC_COMMAND_POSITION_INDEX = 2;
const int TC_ACTUAL_POSITION_INDEX = 3;
public void UserLimitCommandPositionDirectSet()
{
axis = CreateAndReadyAxis(Constants.AXIS_NUMBER);
axis.
MoveVelocity(Constants.VELOCITY, Constants.ACCELERATION);
ConfigureUserLimitInterrupts(USER_LIMIT_FIRST);
ConfigureUserLimitInterrupts(USER_LIMIT_SECOND);
WaitForInterrupts();
}
public void ConfigureUserLimitInterrupts(int userLimitIndex)
{
}
public void WaitForInterrupts()
{
bool done = false;
int timeout_millseconds = 10;
while (!done)
{
switch (eventType)
{
break;
done = true;
break;
default:
break;
}
}
}
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#
const int MATHBLOCK_COUNT = 1;
const int AXIS_COUNT = 2;
const int USER_LIMIT_COUNT = 1;
const int FIRST_AXIS_INDEX = 0;
const int SECOND_AXIS_INDEX = 1;
const double USER_UNITS = 1048576;
const int MATHBLOCK_INDEX = 0;
const int USER_LIMIT_INDEX = 0;
const double MAX_POSITION_DIFFERENCE = 0.5 * USER_UNITS;
const int USER_LIMIT_DURATION = 0;
const double RELATIVE_POSITION = 2 * MAX_POSITION_DIFFERENCE;
const double VELOCITY = 1;
const double ACCELERATION = 10;
const double DECELERATION = 10;
const double JERK_PCT = 0;
USE_HARDWARE = false;
try
{
if (USE_HARDWARE)
{
}
else
{
}
mathBlockConfig.InputAddress0 = axis0.
AddressGet(INPUT_AXIS_ADDRESS_TYPE);
mathBlockConfig.InputDataType0 =
RSIDataType.RSIDataTypeDOUBLE;
mathBlockConfig.InputAddress1 = axis1.
AddressGet(INPUT_AXIS_ADDRESS_TYPE);
mathBlockConfig.InputDataType1 =
RSIDataType.RSIDataTypeDOUBLE;
mathBlockConfig.ProcessDataType =
RSIDataType.RSIDataTypeDOUBLE;
Console.WriteLine("MathBlock configured to subtract the position of the second axis from the position of the first axis.");
Console.WriteLine("UserLimit configured to trigger when the absolute position difference is greater than " + MAX_POSITION_DIFFERENCE + " and abort motion.");
Console.WriteLine("Moving the axes to trigger the UserLimit...");
axis0.
MoveRelative(RELATIVE_POSITION, VELOCITY, ACCELERATION, DECELERATION, JERK_PCT);
{
Console.WriteLine("Both axes are in the error state after the UserLimit triggered (This is the intended behavior).");
return 0;
}
else
{
Console.WriteLine("Error: The axes should be in an error state after the UserLimit triggers, but they are not.");
Console.WriteLine(
"First Axis State: " + axis0.
StateGet());
Console.WriteLine(
"Second Axis State: " + axis1.
StateGet());
return -1;
}
}
catch (Exception e)
{
Console.WriteLine("Error: " + e.Message);
return -1;
}
finally
{
}
- C++
const int NUM_AXES = 2;
const int AXIS_X_INDEX = 0;
const int AXIS_Y_INDEX = 1;
const double MAX_POSITION_DIFFERENCE = 0.5;
const RSIAction USER_LIMIT_ACTION = RSIAction::RSIActionABORT;
const int USER_LIMIT_DURATION = 0;
const double RELATIVE_POSITION = 2 * MAX_POSITION_DIFFERENCE;
const double VELOCITY = 1;
const double ACCELERATION = 10;
const double DECELERATION = 10;
const double JERK_PCT = 0;
int initialUserLimitCount = -1;
int initialMathBlockCount = -1;
int exitCode = -1;
try
{
const int userLimitIndex = initialUserLimitCount;
const int mathBlockIndex = initialMathBlockCount;
: (RSIAxisAddressType::RSIAxisAddressTypeCOMMAND_POSITION);
mathBlockConfig.
Operation = RSIMathBlockOperation::RSIMathBlockOperationSUBTRACT;
std::cout << "MathBlock configured to subtract the position of the second axis from the position of the first axis." << std::endl;
uint64_t mathBlockProcessValueAddress =
controller->
AddressGet(RSIControllerAddressType::RSIControllerAddressTypeMATHBLOCK_PROCESS_VALUE, mathBlockIndex);
userLimitIndex, 0, RSIUserLimitLogic::RSIUserLimitLogicABS_GT, mathBlockProcessValueAddress, MAX_POSITION_DIFFERENCE
);
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;
std::cout << "Moving the axes to trigger the UserLimit..." << std::endl;
axisX->
MoveRelative(RELATIVE_POSITION, VELOCITY, ACCELERATION, DECELERATION, JERK_PCT);
if ((axisX->
StateGet() == RSIState::RSIStateERROR) && (axisY->
StateGet() == RSIState::RSIStateERROR))
{
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;
}
if (initialUserLimitCount != -1) { controller->
UserLimitCountSet(initialUserLimitCount); }
if (initialMathBlockCount != -1) { controller->
MathBlockCountSet(initialMathBlockCount); }