Announcement

Collapse
No announcement yet.

P3K best practices with lots of similar machinery?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts


  • P3K best practices with lots of similar machinery?

    Good day. I'm on my first Productivity3000 project.

    Among other things, I'll have at least 7 generators to control (with potential for way more in the future). I wish to not have 7 copies of the same task laying around. The other platforms I've used in the past allowed me to create custom routines with parameters. Even Do-More had parameterized subroutines.

    Well, the AD "plc picker tool" sent me to the P3k platform due to my remote IO needs. I was surprised to find out they don't seem to even be able to do parameters in their tasks. I read about structures early on, but now I find out you cannot create your own custom structures.

    So, no parameters, no custom structures. What's a guy to do?

    My current plan is to have a 2D array of analogs, and another 2D array of discretes. And so in my main program, I have a FOR loop running which sets an index, and then CALL a task to control the gens. Inside the gen task, i can reference these arrays. GenAnalog(index)(1) becomes the start command, and so on. Readability is in the tank, so far, so i may set up some simple rungs to populate meaningful variable names: -| GenAnalog(index),(1) |----( START_GEN )

    This sort of works, until I get to timers. Timers get all messed up. I'd need at least 7 unique timers. My thought was to create a unique timer struct for each generator, but of course, you can't make an array of TIMER structs. Therefore, I have no way to address them with my "index" pointer var.

    I'd like some advice on the best practice for avoiding having to copy and maintain multiple versions of the same task, and to keep thing D.R.Y.





  • #2
    Hi Telegauge,

    I've been in the same boat as you. It's definitely a hassle. My workaround is to make a task that handles what it needs to, define a few tags that get reused for input and output of that task, and copy the data you need to manipulate into the placeholder tags, call the task, and return the result to the output tags, where you move the result back to wherever it needs to go. Reading what I just typed, it definitely sounds confusing, so I can add a screenshot of what I'm doing if you need.

    Timers, on the other hand, may need to be handled separately, unless you feel like starting a countup timer on power-up and referencing all your timer needs to it. That's more of an arduino way of doing things, but we're in the land of workarounds until we can make our own datatypes and functions.

    Comment



    • #3
      Yeah, I wouldn't mind a screenshot, though I think we're on the same page. I feel like what I have is terribly complex, and a lot of the complexity comes from copying into meaningfully named variables (for readability). But I'd rather put the time in up front getting this sorted properly, so when it comes time to run against real equipment, i won't have to edit between 7 and 25 copies of the same task - just this one.

      I've also run into issues where tasks that have been called, cannot themselves call other tasks.

      The arduino "global timer" is a grand idea. I think I can figure out how to do fail-to-close timers now, which was a critical issue.

      Comment



      • #4
        I do the same as Frontier. Here is a screenshot of a simple task that checks the commanded speed being calculated from an optimization system to make sure it fits within the limits of the machine. It loads a high speed and a low speed into the tags for each conveyor used in the task and the commanded speed from the optimizer. If it is below the low or above the high it will limit to the high or low speed for that conveyor section. Even though it corrects the speed it also will turn on a bit if either high or low are exceeded to notify a technician that something in the optimization system has entered parameters that allow it to try and command something outside of the range of the equipment. It is basically a safety check, but is used for dozens of conveyors so I do not have to repeat the logic over and over.

        Click image for larger version

Name:	TASK_CAll.JPG
Views:	260
Size:	86.6 KB
ID:	120135

        Comment



        • #5
          Yeah, ok, I'm doing the same thing, but with a bunch of contacts and coils. I think I'll move the whole operation into a copy data block. That seems a little more straight forward.

          It's not great, man, but I'll get it to work.

          I just wanted to make sure I wasn't missing something.

          Comment



          • #6
            Hi;
            Hope I am not too late.
            What I've done in the past is to use a two-dimensional array where the 'x' sub-index is your machine, and the other(s) is the corresponding data. This way you only need to pass the machine number and everything else is done inside the same task.
            Hope this helps!

            Comment



            • #7
              Originally posted by juance View Post
              Hi;
              Hope I am not too late.
              What I've done in the past is to use a two-dimensional array where the 'x' sub-index is your machine, and the other(s) is the corresponding data. This way you only need to pass the machine number and everything else is done inside the same task.
              Hope this helps!
              With Do-more, that works by utilizing a block (array) of a User Data Type structure for the machine parameters. Say you have an array of the MachineStruct UDT called MP, where you would index MP[MachineIndex].MotorXYZZY in an OUT coil or MP[MachineIndex].CookTime for a preset to a TMR. You don't have to emulate the structure via a 2nd dimension, just use a strongly-typed 1 dimensional array.
              There are 10 kinds of people in this world, those who know binary, and those who do not.

              Comment



              • #8
                These are the cases when you really need UDTs!

                Comment



                • #9
                  Originally posted by Elcan View Post
                  These are the cases when you really need UDTs!
                  This! Agreed.

                  Comment



                  • #10
                    This sort of works, until I get to timers. Timers get all messed up. I'd need at least 7 unique timers. My thought was to create a unique timer struct for each generator, but of course, you can't make an array of TIMER structs. Therefore, I have no way to address them with my "index" pointer var.
                    got me thinking.



                    I made a simple example project for abstracting out multiple timers using the 'global free running timer method.'
                    Pac version is in the project name.

                    The 'eatscan' loop is present to show the fragility of this concept with respect to scan time.
                    When enabled, monitor CPU scan time.
                    Observe the ElapsedTime array compared to the setting in the Duration array.
                    ----when eatscan is not enabled, some timers will elapse an extra millisecond, when eatscan is enabled, some timers will accumulate multiple milliseconds -- depending on value of 'eatscans' number.
                    (I would guess that a firmware implementation would be less dependent on scan time!)

                    USAGE:
                    Look in the dataview named t. Choose desired values in the Duration array. Start any (abstracted) timer(s) by setting the bit(s) in the Start array.
                    When the 'timer' is started, the Start bit(s) will turn off and the Running bit(s) will turn on.
                    When the 'timer' is complete, the Running bit(s) turn off and the Complete bit(s) turn on.
                    Use the Complete bit(s) as needed and then turn it off when handled.

                    Each rung has some documentation to help convey what the code does - and maybe 'why!'
                    mashva helped me to handle the freerunning timer rollover and suggested the 'eatscan' checking to determine the impact of CPU load on accuracy.
                    If you find anything wrong with rollover handling, please comment.
                    (rollover concept came from here, however the example used UNSIGNED LONG INTS. PAC does not support an unsigned 32 int that is NOT BCD.)


                    Rung 9 is not needed - I already had the hardware configured, so I used it.

                    Arrays are all 10 columns - to emulate 10 timers.
                    Start Trigger an emulated timer
                    Running Timer is timing
                    Complete Timer is complete

                    StartTimestamp Timer beginning timestamp
                    Duration How long a timer is to run
                    ElapsedTime How long a timer has been running

                    Attached Files

                    Comment

                    Working...
                    X