A clever homebrew modulus implementation

前端 未结 5 1813
甜味超标
甜味超标 2021-02-06 04:29

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

相关标签:
5条回答
  • 2021-02-06 04:37

    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)

    0 讨论(0)
  • 2021-02-06 04:38

    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.

    0 讨论(0)
  • 2021-02-06 04:41

    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;
    }
    
    0 讨论(0)
  • 2021-02-06 04:51

    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.

    0 讨论(0)
  • 2021-02-06 04:53

    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.

    0 讨论(0)
提交回复
热议问题