I would like to have a bit in the firmware the represents a “soft” digital input, that is the result of a comparison between some value coming in to the firmware via PDO and a static value.
MathBlocks sounds ideal, because I’d like this input to reflect the instantaneous state of the “soft concept,” and it could be used as a user limit trigger, or anything else in the firmware.
However, MathBlocks don’t support comparison operations.
Is there a good way to accomplish this?
In particular, I want the soft input to be active when actual torque exceeds a threshold value.
I can think of some convoluted ways to accomplish this, but they all use numerous MathBlocks (of which, a global total of 128 is the max). If I have a bunch of axes, and this feature is enabled on all of them, I’ll run out quickly.
Also, what happens when you divide by 0 in the firmware?
Is the operation skipped?
Is a sentinel value used for the quotient?
What happens when you convert a double to an int?
truncation
rounding (floor/ceiling)
Is the sign preserved?
What if you perform a division on two doubles and save the result as an int?
What if the divisor is 0?
SHIFT operation
Which direction does SHIFT move the bits? Can I specify + or - amounts?
Do bits roll over, or are they shifted off?
All my wonky MathBlocks-only solutions raise these questions, but I don’t know what I should expect.
Are UserLimits a better solution for this? They have the concept of comparison in them.
If I created one that did a comparison: greater-or-equal <threshold_value> and set 0x1 on the output address, then another that did the opposite comparison, less-than <threshold_value> and cleared 0x1 on the output address, would this do what I want?
If I configure them so that they are NOT one-shot, will they automatically re-arm/re-fire the next sample?
I’ve had trouble in the past with using two user limits to manage a single value. This may no longer be an issue, but the simpler the solution, the better.
Correction: I was trying to reconfigure a user limit after it fired. I don’t think I’ve had problems driving a single value in firmware memory with two different user limits.
With singleShot: false, the UserLimit remains enabled and is evaluated cyclically in firmware.
One detail to keep in mind: the UserLimit output block writes when the condition is true; it does not automatically clear the bit when the condition becomes false. So if you want a latched “torque exceeded” bit, one UserLimit is enough. If you want the UserBuffer bit to exactly follow the condition, use a second UserLimit to clear the same bit when the opposite condition is true.
For digital_value := |x| >= y, the set condition can be: (x >= y) OR (x <= -y)
and the clear condition can be: (x < y) AND (x > -y)
Then torqueExceeded follows |x| >= y directly. No UserBuffer location, output block, initialization, or second UserLimit is needed.
The proposed output approach is valid when a latched indication is desired. For a live indication, two limits setting and clearing shared storage adds unnecessary state and ordering considerations. Also, singleShot: false means cyclic evaluation, but does not make an output automatically track the condition; their warning about explicitly clearing it is correct.
It is extremely unlikely to change. We really try to avoid ever putting breaking behavior into our releases. If the unlikely event it did change, it would only be because there was significant value added that justified the hassle of forcing customers to rework their code.
I wasn’t trying to suggest that you would flippantly break backward compatibility. Rather, I’m curious if the bits (or this bit, in particular) is a part of the contract between the library and the client.
Some things are not exactly “public,” per se, and no guarantee is implied by how RMP currently does some things under the covers. I didn’t know if some of the status bits are essentially public information that won’t be changed.
What is the advantage of creating an IOPoint object? Does it “belong” to anything in particular? Will it automatically show up as an I/O for an axis, node, or something else?
IOPoint objects might add value by letting you interact with IO in a unified way. Rather than Axis, IO, NetworkNode ,or MotionController method, you can create them and get the same function set. They will not be visual outside of your code and do not belong to anything. I typically would use them mostly for the Controller Memory address use case.
They are just a useful tool rather than something that adds a new capability.
The data reported by RMP (via RapidCode) for this PDO looks correct:
Inputs
Idx Size/Off Type Value F/W Addr Input Name
# 92 16/ 2312 INT 3 (0x031454c8) Drive 0 (Yaskawa Servo).1st Transmit PDO mapping.Torque actual value
The mask is set to 0xFFFF because the PDO is a 16-bit value. When I create “negative” (i.e. directional) torque, the value looks like a normal 2’s complement itneger. However, the user limit fires because (perhaps?) the value is “abs greater” than the limit value (0x96). It doesn’t appear to be treating the PDO value as if it were signed.
Am I doing something wrong? Here’s the code that configured the UL.
// One UserLimit, no output, not single-shot. Its status bit tracks the live comparison.
if (userlimit_torque_limit_si == nullptr) {
userlimit_torque_limit_si = RMPUserLimits_UserLimit();
if (userlimit_torque_limit_si == nullptr) {
_torque_si_enabled = false;
throw std::exception("Cannot configure torque soft input! UserLimit allocation failed!");
}
userlimit_torque_limit_si->SetName(_cat("Motor#", GetMotorIDString(), "_TorqueSI"));
userlimit_torque_limit_si->SetMotionController(mc);
}
int32_t ul = userlimit_torque_limit_si->GetHandle();
const double TRIGGER_IMMEDIATELY = 0.0;
const bool NOT_SINGLE_SHOT = false;
auto logic = _torque_si_is_absolute
? r::RSIUserLimitLogic::RSIUserLimitLogicABS_GT
: r::RSIUserLimitLogic::RSIUserLimitLogicGT;
mc->UserLimitConditionSet(ul, 0, logic, torque_host_addr, pdo_mask, (uint32_t)threshold_native);
mc->UserLimitConfigSet(ul,
r::RSIUserLimitTriggerType::RSIUserLimitTriggerTypeSINGLE_CONDITION,
r::RSIAction::RSIActionNONE, GetAxisIndex(), TRIGGER_IMMEDIATELY, NOT_SINGLE_SHOT);
You can’t tell from the screen shot, but when I induce “positive” torque, the limit behaves as I’d expect and doesn’t fire until the input goes above 96.
When I induce any “negative” torque, the limit fires right away all the time.
I wish we had a way to make UserLimits work with 16 bit values. The Integer overloads are always going to use 32bit. However, here is a workaround for you. Consider using the AND of two conditions. |x| >= threshold Should represent as > threshold and also < 0xFFFF - threshold.
uint32_t pdo_mask = 0xFFFF;
uint32_t threshold = (uint32_t)threshold_native;
// One UserLimit, no output, not single-shot. Its status bit tracks the live comparison.
if (userlimit_torque_limit_si == nullptr) {
userlimit_torque_limit_si = RMPUserLimits_UserLimit();
if (userlimit_torque_limit_si == nullptr) {
_torque_si_enabled = false;
throw std::exception("Cannot configure torque soft input! UserLimit allocation failed!");
}
userlimit_torque_limit_si->SetName(_cat("Motor#", GetMotorIDString(), "_TorqueSI"));
userlimit_torque_limit_si->SetMotionController(mc);
}
int32_t ul = userlimit_torque_limit_si->GetHandle();
const double TRIGGER_IMMEDIATELY = 0.0;
const bool NOT_SINGLE_SHOT = false;
if (_torque_si_is_absolute) {
// signed INT16 absolute compare using the raw masked 16-bit PDO value:
// |x| > threshold -> raw > threshold AND raw < (65536 - threshold)
uint32_t negative_side_exclusive_bound = 0x10000u - threshold;
mc->UserLimitConditionSet(
ul, 0,
r::RSIUserLimitLogic::RSIUserLimitLogicGT,
torque_host_addr,
pdo_mask,
threshold);
mc->UserLimitConditionSet(
ul, 1,
r::RSIUserLimitLogic::RSIUserLimitLogicLT,
torque_host_addr,
pdo_mask,
negative_side_exclusive_bound);
mc->UserLimitConfigSet(
ul,
r::RSIUserLimitTriggerType::RSIUserLimitTriggerTypeCONDITION_AND,
r::RSIAction::RSIActionNONE,
GetAxisIndex(),
TRIGGER_IMMEDIATELY,
NOT_SINGLE_SHOT);
}
else {
mc->UserLimitConditionSet(
ul, 0,
r::RSIUserLimitLogic::RSIUserLimitLogicGT,
torque_host_addr,
pdo_mask,
threshold);
mc->UserLimitConfigSet(
ul,
r::RSIUserLimitTriggerType::RSIUserLimitTriggerTypeSINGLE_CONDITION,
r::RSIAction::RSIActionNONE,
GetAxisIndex(),
TRIGGER_IMMEDIATELY,
NOT_SINGLE_SHOT);
}
If you want |x| >= threshold instead, change the absolute branch to GE threshold and LE (0x10000u - threshold). This assumes threshold is in 1..32767.