9

I'm programming a PLC with some legacy software (RSLogix 500, don't ask) and it does not natively support a modulus operation, but I need one. I do not have access to: modulus, integer division, local variables, a truncate operation (though I can hack it with rounding). Furthermore, all variables available to me are laid out in tables sorted by data type. Finally, it should work for floating point decimals, for example 12345.678 MOD 10000 = 2345.678.

If we make our equation:

dividend / divisor = integer quotient, remainder

There are two obvious implementations.

Implementation 1: Perform floating point division: dividend / divisor = decimal quotient. Then hack together a truncation operation so you find the integer quotient. Multiply it by the divisor and find the difference between the dividend and that, which results in the remainder.

I don't like this because it involves a bunch of variables of different types. I can't 'pass' variables to a subroutine, so I just have to allocate some of the global variables located in multiple different variable tables, and it's difficult to follow. Unfortunately, 'difficult to follow' counts, because it needs to be simple enough for a maintenance worker to mess with.

Implementation 2: Create a loop such that while dividend > divisor divisor = dividend - divisor. This is very clean, but it violates one of the big rules of PLC programming, which is to never use loops, since if someone inadvertently modifies an index counter you could get stuck in an infinite loop and machinery would go crazy or irrecoverably fault. Plus loops are hard for maintenance to troubleshoot. Plus, I don't even have looping instructions, I have to use labels and jumps. Eww.

So I'm wondering if anyone has any clever math hacks or smarter implementations of modulus than either of these. I have access to + - * /, exponents, sqrt, trig functions, log, abs value, and AND/OR/NOT/XOR.

Ben Mordecai
  • 685
  • 2
  • 8
  • 19
  • 1
    You really can't use loops? That seems odd, since pretty much all interesting programs have to be able to loop. – templatetypedef Feb 12 '13 at 20:36
  • Is there any guarantee how much the input changes across program loops? – John Dvorak Feb 12 '13 at 20:37
  • 3
    @templatetypedef PLC uses one big implicit loop around the entire program, and only updates the I/O buffers at the end/start – John Dvorak Feb 12 '13 at 20:37
  • 2
    @templatetypedef PLC programs focus very little on data processing and very heavily on realtime operation. The style is more like, "when sensor A is triggered, index servo B to the receive position, upon reaching the position, fire solenoid valve C for 500 ms, then release and fire solenoid D." So basically a loop hijacks program control, and you risk getting stuck so this whole sequence doesn't work. – Ben Mordecai Feb 12 '13 at 20:39
  • @JanDvorak there isn't a guarantee, and in fact the modulus function is a part of a larger computation. Basically, I'm capturing an encoder position and storing it to a shift register along with a few other parameters, and my encoder overflows at 10,000 pulses, so if you detect the rising edge and the falling edge, right at the wrap point you would have to fix that scenario. – Ben Mordecai Feb 12 '13 at 20:42
  • There should be no need to use an index for this loop, so I don't quite understand the problem with implementation 2. – argentage Feb 12 '13 at 20:56
  • @airza It's not a real problem, it's a contrived problem. It's bad form in my industry and people don't like it. I'm confident I could write a failsafe loop, it's just considered very bad practice. – Ben Mordecai Feb 12 '13 at 20:59
  • Fair enough. do you have any specific constraints for the modulus? – argentage Feb 12 '13 at 21:00
  • They are very generous. I don't need to handle negative numbers. Essentially I have a conveyor belt that can be in a position from 0 to 10,000 inches, so if I detected a product at 9994.3 inches and needed to add 12 inches to that position I could MOD it to get 6.3. In that case I could just use a conditional statement, but ideally I could get a reusable function. – Ben Mordecai Feb 12 '13 at 21:05
  • perhaps you should assume the truncated part changes by at most one per clock tick. That is, cache the quotient and accept occasional errors when the value tracked changes too fast. Seriously, how fast is the loop? Let's be generous and assume 100ms (yuck). I don't think 100kin/s is realistic. Not even close. – John Dvorak Feb 12 '13 at 21:20
  • @JanDvorak the loop isn't fast, it's just a single computation in a sequence of events. Since its all just math and accounting, it seems like it should be possible to do it in one quick scan, but if not I would have to rewrite the section as a state machine. – Ben Mordecai Feb 12 '13 at 21:25
  • @BenMordecai still, 10kilo-inch of belt / 100ms per loop = 7 times the speed of sound. That's not realistic. – John Dvorak Feb 12 '13 at 21:28
  • @JanDvorak it's not running that speed. It's a position. It's just set to overflow at 10,000 inches. – Ben Mordecai Feb 12 '13 at 21:34
  • Implementation #1 is the solution that you should use for any realtime/PLC implementation. Bite the bullet and do the truncation. Variable-length loops are definitely not the way to go. – RBarryYoung Feb 12 '13 at 21:37
  • @JanDvorak also, I'm looking back at my parameters and realizing that in many cases I'll use the MOD function for encoder pulses directly, and it's fairly reasonable to get thousands of pulses per inch. – Ben Mordecai Feb 12 '13 at 21:38
  • Can you provide a log-reasonable upper bound on the absolute change of the value per main loop? – John Dvorak Feb 12 '13 at 21:40
  • @RBarryYoung that's definitely my fallback. I was hoping that a math genius might swoop in with something clever with binary operations, exponents, base conversion or something, but I'll settle for implementation 1. – Ben Mordecai Feb 12 '13 at 21:40
  • @JanDvorak I'm not on the same line of thinking as you right now. I don't understand the relevance of controller speed. I can save a snapshot of the position at any given moment that I need it and I have all day to run the calculations, I'm mainly just looking for a clean way run that operation among a sequence of other math operations. – Ben Mordecai Feb 12 '13 at 21:44
  • Hmm, OK then, some questions: 1) are your values really floating-point or really decimal? The difference is very important for this kind of thing. 2) What are the arithmetic operations available to you? 3) which, if any, of (2) return more than one result value? – RBarryYoung Feb 12 '13 at 21:45
  • Is 10000 the only divisor you are about? How are floating point numbers laid out in memory? How are the bitwise AND/OR/NOT/XOR operations defined? – Joni Feb 12 '13 at 22:47
  • @Joni The bitwise operation are implemented according to this manual around page 225. http://literature.rockwellautomation.com/idc/groups/literature/documents/rm/1766-rm001_-en-p.pdf I believe that the floating point layout is ISO, 32 bit – Ben Mordecai Feb 13 '13 at 03:08
  • @RBarryYoung, A complete list is in the manual I linked Joni to. The set compatible with a "compute" instruction are + - * /, exponents, sqrt, trig functions, log, abs value, and AND/OR/NOT/XOR. – Ben Mordecai Feb 13 '13 at 03:09
  • @RBarryYoung I forgot to mention, I do need floating point support – Ben Mordecai Feb 13 '13 at 03:17
  • @BenMordecai Instead of pointing us to a 700 page manual, this would work better if you just told us the answer to our questions. – RBarryYoung Feb 13 '13 at 04:34
  • @RBarryYoung, I already did, twice. The manual exists for the sake of being comprehensive. You're under no obligation to answer the question. – Ben Mordecai Feb 13 '13 at 04:55
  • @BenMordecai Could you elaborate on the actual problem? There is probably a better solution than a modulus calculation. – J... Feb 13 '13 at 11:26
  • @J... it's for unwinding a wrap around position. – Ben Mordecai Feb 13 '13 at 13:13

5 Answers5

6

How many bits are you dealing with? You could do something like:

if dividend > 32 * divisor  dividend -= 32 * divisor
if dividend > 16 * divisor  dividend -= 16 * divisor
if dividend > 8 * divisor  dividend -= 8 * divisor
if dividend > 4 * divisor  dividend -= 4 * divisor
if dividend > 2 * divisor  dividend -= 2 * divisor
if dividend > 1 * divisor  dividend -= 1 * divisor
quotient = dividend

Just unroll as many times as there are bits in dividend. Make sure to be careful about those multiplies overflowing. This is just like your #2 except it takes log(n) instead of n iterations, so it is feasible to unroll completely.

Keith Randall
  • 22,985
  • 2
  • 35
  • 54
  • Interesting. So essentially this is emulating the behavior of a loop but without actually using one? I'm not sure I understand how this works, but assuming it does, it is quite clever! – Ben Mordecai Feb 13 '13 at 03:13
  • Also, I take it that this only works in integers, since it's based on powers of two that don't follow a floating point data pattern? – Ben Mordecai Feb 13 '13 at 15:27
  • The same logic should work for floats as well. The number of iterations you need is log_2(max(type)/min(type)) which for floats is significantly larger (~256 for 32-bit floats, ~2048 for 64-bit floats). Precision becomes a problem long before that many iterations, however. – Keith Randall Feb 13 '13 at 16:05
5

If you don't mind overly complicating things and wasting computer time you can calculate modulus with periodic trig functions:

atan(tan(( 12345.678 -5000)*pi/10000))*10000/pi+5000   =   2345.678

Seriously though, subtracting 10000 once or twice (your "implementation 2") is better. The usual algorithms for general floating point modulus require a number of bit-level manipulations that are probably unfeasible for you. See for example http://www.netlib.org/fdlibm/e_fmod.c (The algorithm is simple but the code is complex because of special cases and because it is written for IEEE 754 double precision numbers assuming there is no 64-bit integer type)

Joni
  • 108,737
  • 14
  • 143
  • 193
  • Believe it or not this is actually an ideal solution for me. Computation time is a non-issue. The computation only occurs once per second or two so the efficiency is a non issue. It fits in a single compute instruction, which I can simply comment "This is an obscure way to calculate modulus." It's not sensitive to the rung ordering either. I like it a lot. – Ben Mordecai Feb 13 '13 at 18:32
3

This all seems completely overcomplicated. You have an encoder index that rolls over at 10000 and objects rolling along the line whose positions you are tracking at any given point. If you need to forward project stop points or action points along the line, just add however many inches you need and immediately subtract 10000 if your target result is greater than 10000.

Alternatively, or in addition, you always get a new encoder value every PLC scan. In the case where the difference between the current value and last value is negative you can energize a working contact to flag the wrap event and make appropriate corrections for any calculations on that scan. (**or increment a secondary counter as below)

Without knowing more about the actual problem it is hard to suggest a more specific solution but there are certainly better solutions. I don't see a need for MOD here at all. Furthermore, the guys on the floor will thank you for not filling up the machine with obfuscated wizard stuff.

I quote :

Finally, it has to work for floating point decimals, for example 12345.678 MOD 10000 = 2345.678

There is a brilliant function that exists to do this - it's a subtraction. Why does it need to be more complicated than that? If your conveyor line is actually longer than 833 feet then roll a second counter that increments on a primary index roll-over until you've got enough distance to cover the ground you need.

For example, if you need 100000 inches of conveyor memory you can have a secondary counter that rolls over at 10. Primary encoder rollovers can be easily detected as above and you increment the secondary counter each time. Your working encoder position, then, is 10000 times the counter value plus the current encoder value. Work in the extended units only and make the secondary counter roll over at whatever value you require to not lose any parts. The problem, again, then reduces to a simple subtraction (as above).

I use this technique with a planetary geared rotational part holder, for example. I have an encoder that rolls over once per primary rotation while the planetary geared satellite parts (which themselves rotate around a stator gear) require 43 primary rotations to return to an identical starting orientation. With a simple counter that increments (or decrements, depending on direction) at the primary encoder rollover point it gives you a fully absolute measure of where the parts are at. In this case, the secondary counter rolls over at 43.

This would work identically for a linear conveyor with the only difference being that a linear conveyor can go on for an infinite distance. The problem then only needs to be limited by the longest linear path taken by the worst-case part on the line.

With the caveat that I've never used RSLogix, here is the general idea (I've used generic symbols here and my syntax is probably a bit wrong but you should get the idea)

RSLogix Sample

With the above, you end up with a value ENC_EXT which has essentially transformed your encoder from a 10k inch one to a 100k inch one. I don't know if your conveyor can run in reverse, if it can you would need to handle the down count also. If the entire rest of your program only works with the ENC_EXT value then you don't even have to worry about the fact that your encoder only goes to 10k. It now goes to 100k (or whatever you want) and the wraparound can be handled with a subtraction instead of a modulus.

Afterword :

PLCs are first and foremost state machines. The best solutions for PLC programs are usually those that are in harmony with this idea. If your hardware is not sufficient to fully represent the state of the machine then the PLC program should do its best to fill in the gaps for that missing state information with the information it has. The above solution does this - it takes the insufficient 10000 inches of state information and extends it to suit the requirements of the process.

The benefit of this approach is that you now have preserved absolute state information, not just for the conveyor, but also for any parts on the line. You can track them forward and backward for troubleshooting and debugging and you have a much simpler and clearer coordinate system to work with for future extensions. With a modulus calculation you are throwing away state information and trying to solve individual problems in a functional way - this is often not the best way to work with PLCs. You kind of have to forget what you know from other programming languages and work in a different way. PLCs are a different beast and they work best when treated as such.

J...
  • 30,968
  • 6
  • 66
  • 143
  • The reason is because it decreases the power of the implementation. It can only cases where a single subtraction would give the result, otherwise it would need a loop, which is implementation two. Your solution is essentially to change the while to an if, which works for that specific range. I'm looking for a reusable function, because I've had this problem enough and in some cases I have needed to unwind a position that was wrapped around more than once. – Ben Mordecai Feb 13 '13 at 13:35
  • @BenMordecai this is what I was saying with the secondary counter. All you need is to keep an integer counter that rolls over in the same way. I've added more detail. – J... Feb 13 '13 at 14:16
  • My encoder rollover is handled by a high speed counter subsystem in real time. My MOD function is only to be used in event handling that creates a need for a single computation. It's frustrating that there doesn't appear to be an easy way to solve is computationally, but a conditional approach does work in the bulk of use cases I'll encounter. – Ben Mordecai Feb 13 '13 at 15:25
  • I think by use of the counter, are you not essentially using the program scan *as* a loop, with the counter serving to let you pass out of a 'unwind computation in progress' state? Not that there's anything wrong with it, I'm just trying to understand if that is what you mean. – Ben Mordecai Feb 13 '13 at 15:31
  • @BenMordecai The program scan *is* a loop, but I'm not using it *as* a loop in any sense, no. At any given point your PLC is aware of the state of the encoder - it knows the position as a value somewhere between 0-10k. At one and only one point in time will the new value be smaller than the previous value while travelling in one direction - when the encoder rolls over. I'm saying that by using this single event to increment a counter you can know where the conveyor is over a broader range than 0-10k. – J... Feb 13 '13 at 18:16
  • @BenMordecai Knowing this, you can then write your program in the extended coordinate set. For example, if your counter is at 3 and your encoder is at 9500 then your position in the extended state is 3x10k + 9500 = 39500. If you want to take action 12000 inches from here you simply set a target for 51500 (ie: counter at 5 and encoder at 1500). Do all the work in the extended system. Then you only have to handle the wrap-around once (turning MOD into SUB) and by making your extended system big enough it can happen such that no single part can span two big rollovers. – J... Feb 13 '13 at 18:19
3

You can use a subroutine to do exactly what you are talking about. You can tuck the tricky code away so the maintenance techs will never encounter it. It's almost certainly the easiest for you and your maintenance crew to understand.

It's been a while since I used RSLogix500, so I might get a couple of terms wrong, but you'll get the point.

Define a Data File each for your floating points and integers, and give them symbols something along the lines of MOD_F and MOD_N. If you make these intimidating enough, maintenance techs leave them alone, and all you need them for is passing parameters and workspace during your math.

If you really worried about them messing up the data tables, there are ways to protect them, but I have forgotten what they are on a SLC/500.

Next, defined a subroutine, far away numerically from the ones in use now, if possible. Name it something like MODULUS. Again, maintenance guys almost always stay out of SBRs if they sound like programming names.

In the rungs immediately before your JSR instruction, load the variables you want to process into the MOD_N and MOD_F Data Files. Comment these rungs with instructions that they load data for MODULUS SBR. Make the comments clear to anyone with a programming background.

Call your JSR conditionally, only when you need to. Maintenance techs do not bother troubleshooting non-executing logic, so if your JSR is not active, they will rarely look at it.

Now you have your own little walled garden where you can write your loop without maintenance getting involved with it. Only use those Data Files, and don't assume the state of anything but those files is what you expect. In other words, you cannot trust indirect addressing. Indexed addressing is OK, as long as you define the index within your MODULUS JSR. Do not trust any incoming index. It's pretty easy to write a FOR loop with one word from your MOD_N file, a jump and a label. Your whole Implementation #2 should be less than ten rungs or so. I would consider using an expression instruction or something...the one that lets you just type in an expression. Might need a 504 or 505 for that instruction. Works well for combined float/integer math. Check the results though to make sure the rounding doesn't kill you.

After you are done, validate your code, perfectly if possible. If this code ever causes a math overflow and faults the processor, you will never hear the end of it. Run it on a simulator if you have one, with weird values (in case they somehow mess up the loading of the function inputs), and make sure the PLC does not fault.

If you do all that, no one will ever even realize you used regular programming techniques in the PLC, and you will be fine. AS LONG AS IT WORKS.

Jason Kennaly
  • 622
  • 1
  • 5
  • 9
0

This is a loop based on the answer by @Keith Randall, but it also maintains the result of the division by substraction. I kept the printf's for clarity.

#include <stdio.h>
#include <limits.h>
#define NBIT (CHAR_BIT * sizeof (unsigned int))

unsigned modulo(unsigned dividend, unsigned divisor)
{
unsigned quotient, bit;

printf("%u / %u:", dividend, divisor);

for (bit = NBIT, quotient=0; bit-- && dividend >= divisor; ) {
        if (dividend < (1ul << bit) * divisor) continue;
        dividend -= (1ul << bit) * divisor;
        quotient += (1ul << bit);
        }
printf("%u, %u\n", quotient, dividend);
return dividend; // the remainder *is* the modulo
}

int main(void)
{
modulo( 13,5);
modulo( 33,11);
return 0;
}
wildplasser
  • 43,142
  • 8
  • 66
  • 109