Monitor Limit Switches to Index Count

jordanat2012

Member
Join Date
Aug 2018
Location
Illinois
Posts
22
Hi all,
I'm working on developing a 90° valve actuator system that will actuate valves several hundred times for some testing. The PLC (an AB Micro820 in this case) controls a DC motor that actuates the valve, and monitors two limit switches built into the actuator. There's one limit switch for each direction (CW / CCW).


I've got the actuator turning back and forth, stopping at the limit switch and pausing for a brief second, then reversing. But my counter isn't working how I hoped and I'm banging my head against a wall trying to figure it out.


Basically, I want to index a counter by one each time the rotation makes one limit switch, then the other (the valve will go from fully closed to fully open, then back again, or vice-versa).


I've attached a screenshot of what I've got right now. My plan was to SET a Boolean contact each time a limit switch is made, and when both limit switches are made, index the counter by one and reset the Booleans. However, it appears to be indexing each time it hits a limit switch.


Any advice? Surely there's something very minor I'm missing?

ActuatorIndex.JPG
 
That was the intent of the SET coil, to have them both active at the same time, then have them RESET in the following rung. Am I looking at that process correctly?
 
That was the intent of the SET coil, to have them both active at the same time, then have them RESET in the following rung. Am I looking at that process correctly?

Suggestion - extend the timers to 60s and watch what happens to the online status - you will then see why you are experiencing
However, it appears to be indexing each time it hits a limit switch
 
If I understand correctly, you want to increment the counter by 1 for every complete cycle i.e. by 1 for each [one completed CW move + one completed CCW move]. Is that right?





Code:
      CCW_Limit                      LimitONS
---+-----] [-----+----------[ONS}------( )-------
   |             |
   |   CW_Limit  |
   +-----] [-----+


       Store     LimitONS
---+---] [---+-----]/[-----+---+---[CTU]---+---
   |                       |   |           |
   |  Store     LimitONS   |   |   Store   |
   +---]/[---+-----] [-----+   +----( )----+
That CTU will see a rising edge once per complete cycle.


I think it's too messy visually and there are probably better ways, but a flip-flop is what came to mind first, and it's easy to understand.


We could put a TON between the CCW_/CW_Limit OR and the one-shot, to have it pause at each limit swtich, and I think Store could be the toggle that drives the CCW_/CW_Control.0 discrete outputs.





I am still wrapping my head around your code; is yours integrated with whatever output are driving the motion (CCW_Control.0 and CW_Control.0, perhaps)?
 
Surely there's something very minor I'm missing?


Maybe. When the the CCW limit switch is made (the valve hits its CCW limit), is _IO_EM_DI_05/CCW_Limit a 1 or a 0? I would guess it's a 0 when made, so the [XIC CCW_Limit] is true when made.


And does the CW limit switch behave the same way?



And don't call me Shirley.
 
Suggestion - extend the timers to 60s and watch what happens to the online status - you will then see why you are experiencing
Thanks Moggie! Good call on that - slowing it down really did help a lot!


If I understand correctly, you want to increment the counter by 1 for every complete cycle i.e. by 1 for each [one completed CW move + one completed CCW move]. Is that right?
Yes, that's exactly what I want to do!


I think it's too messy visually
Yeah, I'm sure it is - I'm not a very fluent PLC programmer by admission. I usually try to fumble my way through to what I need, knowing full well that there are easier paths. And that's why I come to the titans of PLCtalk! It's how I learn...


I am still wrapping my head around your code; is yours integrated with whatever output are driving the motion (CCW_Control.0 and CW_Control.0, perhaps)?
Yes, it is. "CW_Control.0" and "CCW_Control.0" are bytes that turn on and off the intended direction of rotation.


I was messing around with this a bit yesterday, and added the two Reverse Contacts to rung 3. That got me where I needed, except for the last actuation - on the final actuation, it only performs half the turn (90° in one direction only).

Capture.JPG
 
Last edited:
To be honest, I think this approach is not redeemable without a lot of, well, fumbling around and (sorry) silliness, e.g. the addition of the XIOs downstream of the Index_Counter.Q.

1) Bit CCW_Control.0 is redundant with LV_MovingCCW, so the latter could be dropped.
2) Same for CW_Control.o and LV_MovingCW.

Excuse me for making an example this of code, and please be assured this is not aimed at you personally (because I am sure that many of us, and I am certain that I, have been there), but this another reason is why some folks may be wary of Set/Reset (Latch/Unlatch)*: they make 1s and 0s hang around like stale f@rts, and if you are not careful, you end up jumping through hoops, backwards and in a straightjacket, to make them behave the way you want, because they drew you in with the idea that they can solve the problem.

* Only wary; this is not at all a screed against Latch/Unlatch, but at the code needed to drive them.

Okay, on to the code, I'd like to try duck debugging. Actually, this is reverse duck debugging i.e. I am describing what I see the code doing. For actual duck debugging you should be doing it because you know what you expect the code to do. In fact, if you stop reading this (I am pretty sure it is going to be long, and long-winded) and do your own duck debugging, I predict your next post would be "Solved it."

As my brother says, "The only thing worse than the code doing other than what you want it to do, is when it does exactly what you told it to do." To my mind, inculcating that mindset, which is essentially humility, is a major key to successful computer programming, and especially debugging, in any language.

(Yes, definitely long-winded;))

Overall

Looking at the first two rungs, these appear to be what I call ping-pong timers: the completion of one starts the other, and vice versa. The ping-pong (back and forth) is driven by the LV_MovingCW/CCW (= CW_/CCW_Control.0 discrete outputs') bits, and each timer is reset when its limit switch (CW_/CCW_Limit) discrete input bit becomes 1.

What is interesting to me is that

  • they are almost** exactly symmetrical; that is rarely the case with a ping-pong set up, and I think it may be the reason this code fails to operate as you want it to.
    • ** I say almost because the only asymmetric aspect is that one is executed before the other during each scan.
  • they are set up as literal TONs i.e. ON-delay Timers, they delay the start - ON-ing - of the discrete output bit. I say that because in an earlier post it is stated "I've got the actuator turning back and forth, stopping at the limit switch and pausing for a brief second, then reversing." That is, each half-cycle is described with the pause at the end, not the beginning. Since this is a repeating cycle, that may be only a semantic difference, but we'll see.

The last rung is the denouement, that detects when there have been a ping and a pong i.e. one valve open-close cycle. I don't understand why the preset value is 1, when there are supposed to be hundreds of these cycles; perhaps there is other code not shown?

Detail - duck debugging

The Auto_Run bit controls all three rungs, so if Auto_Run is off, then

  • both CW_/CCW_Control.0 discrete output bits are 0
    • as are their redundant twins, LV_MovingCW/CCW),
  • so the valve does not move,
  • the timers are reset
  • The states of LV_CW/CCW_Count are unknown
    • but given that they are rarely 1 for more than a scan or two it is likely that they are both 0
      • We may need to revisit this later
    • So LV_CounterIndex is also probably 0
  • The states of the CW_/CCW_Limit discrete input bits are unknown; there are three possibilities:
    1. The valve is at the CW limit:
    2. The valve is at the CCW limit:
    3. The valve is in between the limits:
When Auto_Run goes from 0 to 1, what happens will depend on which of the three limit cases is current:

Case 1: start at CW limit; CW_LIMIT=1; CCW_LIMIT=0

  • Rung 1 TON_1 is inhibited by [XIO CW_Limit=1]
  • Rung 2 TON_2 starts timing
    • Neither discrete output changes until TON 2 expires
    • When TON 2 does expire
      • CCW_Control.0 becomes 1, and stays 1
        • Which inhibits TON_1 until it becomes 0 again
        • the valve moves off of the CW limit,
        • Which assigns 0 to CW_Limit,
        • but TON_1 does not start because CCW_Control.0 stays 1
      • LV_CCW_Count is Set to 1
  • Rung 3 Index_Counter does nothing
Eventually the valve reaches the CCW Limit

  • CCW_Limit becomes 1
    • This resets TON_2, which sets CCW_Control.0 to 0, and stops driving the valve CCW
  • On the next scan, with CCW_Control.0=0 and CW_Limit=0, TON_1 starts timing
  • When TON_1 expires
    • CW_Control.0 becomes 1
      • The valve starts moving back toward the CW limit
      • But TON 2 remains inhibited, after the valve leaves the CCW limit and the [XIO CCW_Limit=0] on rung 2 evaluates to true, by the [XIO CW_Control.0=1]
    • LV_CW_Count is Set to 1
      • With LV_CCW_Count already Set to 1, [XIC LV_CW_Count=1 XIC LV_CCW_Count=1] rising-edge triggers Index_Counter CTU on Rung 3 to count to 1 and sets bit Index_Counter.Q=1, but the [XIO CW_Control.0=1] downstream of Index_Counter.Q inhibits the resets downstream of that until later.
Eventually the valve reaches the CW limit

  • CW_Limit becomes 1
    • This resets TON_1
      • So CW_Control.0 is set to 0, stopping physical valve movement, but also starting TON_2 on the next rung
  • On Rung 3, this the [XIO CW Control.0=0] is now true, and passes the Index_Counter.Q=1 to the Reset instructions to Reset both LV_CW_/CCW_COUNT both to 0, and also assigns 1 to LV_CounterIndex, which will reset CTU Index_Counter on the next scan.
    • This raises an interesting point: since the PV on the CTU is 1, and LV_CW_/CCW_Count are only Reset downstream of the CTW after Index_Counter.Q becomes 1, which means 1 rising edge was counted, how is the CTU's presencedifferent from nothing, and the entire rung could start with only [XIC LV_CW_Count XIC LV_CCW_Count] directly feeding the [XIO LV_MovingCW XIO ...] and the rest?
At this point the process is back almost to where it started: CW_Limit=1; CCW_Limit=0; both outputs CW_/CCW_Control.0=0, both count bits LV_CW/CCW_Count=0; LV_CounterIndex=1 but that will be cleared on the next scan.

We don't know where the complete cycle count occurs, but if it is edge-triggered on LV_CounterIndex, then I suspect the counting will be correct in this case i.e. where the processed started at the CW limit.

Case 2: start at CCW limit; CW_LIMIT=0; CCW_LIMIT=1

This is similar to Case 1.

  • TON_1 starts timing immediately,
  • While TON_2 is inhibited by [XIO CCW_LIMIT=1] on the initial scans i.e. until TON_1 expires and the valve starts moving
  • Once TON_1 expires
    • LV_CCW_Count is Set to 1
    • CW_Control.0 becomes 1
    • TON_2 remains inhibited by [XIO CW_Control.0=1] and the valve moves off the CCW limit, which movement assigns 0 to CCW_Limit.
  • The valve eventually reaches the CW limit
    • Assigning CW_Limit=1, so
      • TON_1 is reset
      • CW_Control.0 is assigned 0
        • stopping the valve, and
        • Enabling TON_2 to start timing on the same scan
  • When TON_2 expires
    • CCW_Control.0 is assigned 1
      • So the valve starts moving CCW
      • and TON_1 is also inhibited
    • LV_CCW_Count is Set to 1
      • Providing the rising edge to the CTU, since LV_CW_Count is already 1
        • So Index_Counter.Q becomes 1
        • But the outputs of Rung 3 are delayed until CCW_Control.0 becomes 0 in the next step.
  • When the valve hits the CCW Limit
    • CCW_Limit becomes 1
      • which resets TON_2,
        • which in turn assigns 0 to CCW_Control.0
          • which again in turn passes Index_Counter.Q to outputs of Rung 3, assigning 1 to LV_CounterIndex and Resetting both LV_CW/CCW_Count bits to 0
Which takes us back to the starting point of this Case 2, although it will be another scan before LV_CounterIndex becomes 1.

Again, we do not know what triggers the count. If it is the rising edge of LV_CounterIndex, then it should count complete cycles. However, if it is the rising edge of the AND [XIC LV_CW_Count XIC LV_CCW_Count], then count might be complete at either of the red steps above and not complete the last CW (case 1) or CCW (case 2) movement.

Case 3: start at neither limit CW_LIMIT=0; CCW_LIMIT=0

  • In this case, both timers start timing as soon as Auto_Run goes to 1, but TON_1's expiry is detected first on Rung 1, which assigns 1 to CW_Control.0, which
    • resets TON_2 before its expiry can be detected on Rung 2, and
    • starts the valve moving toward the CW limit.
From there it should run the same as Case 1 after the TON_1 expiry.

Conclusion

So it looks like the code could work. Since it does not, the most likely reason is that I missed something and the code is doing what you told it to do and not what I expect it to do.

It could also be that we don't know something about how the complete cycles are counted; I suggested one scenario for the half-cycle miscount above i.e. not triggering the count on the rising edge of LV_CounterIndex.
 
This has more rungs (5 vs. 3) and instructions (24 vs.22) than yours, but still looks cleaner to me, because the functions are separated:

  • detect limit
  • hold timer at limit
  • toggle direction at hold timer expiry
  • move valve
Also it has only one timer; the advantage there is that the interaction between two timers does not have to be grokked.



It's untested, of course, so caveat emptor. It's also not perfect:

  • if the process starts at a limit, I think it will do a direction flip-flop, then a hold, then another direction flip-flop, all on the rising edge of the Auto_Run;
    • It would take at least one rung to fix that.
  • it probably still has the half-cycle count problem, depending how the complete cycle counter is implemented.


[update: took care of those problems; added counter that always finishes at the CW limit; also, it's down to 22 instruction before the counter, the same as yours.]



The TON rung is a mess because MicroLogix 8xx has no Timer.TT bit.


I am not sure about the counter. I would probably have it clock on AnyLimit.Q and set the PV to twice the number of complete cycles desired, plus 1, just to be robust, but it could trigger on [XIC HoldTimer.Q XIC Direction], although that result will depend on where the system starts, or more specifically, will always stop at the CW limit.




Code:
=================================================
Detect either limit of valve motion as a one-shot
=================================================

                         [ AnyLimit ]
        CW_Limit         [  R_TRIG  ]
-----+----] [------+-----[CLK      Q]-----
     |             |
     |  CCW_Limit  |
     +----] [------+


=======================================
Hold Timer starts at either limit event
- HoldTimer.Q will be a one-shot i.e.
  1 for one scan at a time, else 0
=======================================

                                                 [ HoldTimer ]
 Auto_Run       AnyLimit.Q      HoldTimer.Q      [   TON     ]
----] [-------+----] [------+------]/[-----+-----[IN        Q]---+----
              |             |              |     [           ]   |
              | HoldTimerTT |              |   ?-[PT       ET]   |
              +----] [------+              |                     |
                                           |      HoldTimerTT    |
                                           +----------( )--------+


===========================================================
Flip-Flop of direction, clocked (triggered) by timer expiry
===========================================================

 Auto_Run      Direction  HoldTimer.Q           Direction
----] [-----+----] [----------]/[--------+---------( )------
            |                            |
            |  Direction  HoldTimer.Q    |
            +----]/[----------] [--------+


===================================
Control valve motion; stop at limit
===================================

 Auto_Run    Direction    CW_Limit         CW_Control.0
----] [---+-----] [----------]/[---------------( )--------+---
          |                                               |
          |  Direction    CCW_Limit        CCW_Control.0  |
          +-----]/[----------]/[---------------( )--------+


=======================================
Count cycles on rising edge of CW limit
- Stop auto run at counter expiry
=======================================

                            [ Cycle_Counter ]
         CW_Limit           [     CTU       ]     Auto_Run
   ---------] [-------------[CU            Q]-------(U)-----
                            [               ]
   Rising edge of Auto_Run?-[RESET        CV]
                            [               ]
                          ?-[PV             ]
 
Last edited:
I figured out the answer to the query from the original post of this thread: For the CW rung, the CCW_Limit test needs to be an XIC before the timer, the [XIO CW_Limit] test need to go after the timer, and the CW_Control.0 bit needs to seal-in the timer.


See below; the second rung is the basic idea for the CCW rung; the first rung is the same with extra branches needed for initialization; the original third rung is gone, and in its place is the twice-N complete cycle counter.


The counter could also be written to count only CW limit rising edges, but then there would need to be a Reset and a Set for a "have we had one CCW limit event yet?" bit e.g. [XIC AutoRunONS.Q RESET HadCCWLimit] and [XIC CCW_Limit SET HadCCWLimit] and then replace the [>] with [XIC HadCCWLimit] in the path to CycleCount.CU, and remove the ORed [XIC CCW_Limit] branch feeding the CycleCount CTU.





Code:
===============================================
Hold at CCW_Limit for TON, then start CW motion
- If Auto_Run has rising edge
  between limits, start CW motion
===============================================

                                       _______
                                      [ TON_1 ]
 Auto_Run         CCW_Limit           [  TON  ]  CW_Limit   CW_Control.0
----] [---+----+-----] [---------+----[IN    Q]-----]/[---+---( )-------
          |    |                 |    [       ]           |
          |    |  CW_Control.0   |    [PT   ET]           |
          |    +-----] [---------+    [_______]           | 
          |   ____________                                |
          |  [ AutoRunONS ]                               |
          |  [   R_TRIG   ]           CW_Limit            |
          +--[CLK        Q]-------+------]/[-----+--------+
             [____________]       |              |
                                  |   CCW_Limit  |
                                  +------]/[-----+

===============================================
Hold at CW_Limit for TON, then start CCW motion
===============================================

                                       _______
                                      [ TON_2 ]
 Auto_Run         CW_Limit            [  TON  ]  CCW_Limit  CCW_Control.0
----] [--------+-----] [--------+-----[IN    Q]-----]/[-------( )-------
               |                |     [       ]
               |  CCW_Control.0 |     [PT   ET]
               +-----] [--------+     [_______]

============================
Count all limit rising edges
- PV will be twice number of
  desired complete cycles
- only after a CCW limit
  has been counted
===========================

                                                        ____________
                            _______                    [ CycleCount ]
      CW_Limit             [   >   ]                   [     CTU    ]  Auto_run
---+-----] [---------------[EN   o1]---+---------------[CU         Q]-----{R)---
   |                       [       ]   |               [            ]
   |         CycleCount.CV-[i1     ]   |  AutoRunONS.Q-[RESET     CV]
   |                       [       ]   |               [            ]
   |                     0-[i2     ]   |            2N-[PV          ]
   |                       [_______]   |               [____________]
   |  CCW_Limit                        |
   +-----] [---------------------------+
 
I figured out the answer to the query from the original post of this thread: ...


Not quite right:

  • the CW_Control.0 and CCW_Control.0 seal-ins have to go around the TONs.
  • The ORed XIO checks of CW_/CCW_Limit on the rising edge of Auto_Run should be ANDed




I'll have an updated solution shortly.
 
Last edited:
Here are the fixes, plus a .RSS and .PDF of a working RSLogix version, with simulator.


Code:
===============================================
Hold at CCW_Limit for TON, then start CW motion
- If Auto_Run has rising edge
  between limits, start CW motion
===============================================

                             _______
                            [ TON_1 ]
 Auto_Run    CCW_Limit      [  TON  ]          CW_Limit   CW_Control.0
----] [---+-----] [---------[IN    Q]-------+-----]/[---+---( )-------
          |                 [       ]       |
          |                 [PT   ET]       |
          |                 [_______]       |
          |                                 |
          |     CW_Control.0                |
          +----------] [--------------------+
          |                                 |
          |                                 |
          |     ____________                |
          |    [ AutoRunONS ]               |
          |    [   R_TRIG   ]   CCW_Limit   |
          +----[CLK        Q]------]/[------+
               [____________]

===============================================
Hold at CW_Limit for TON, then start CCW motion
===============================================

                                  _______
                                 [ TON_2 ]
 Auto_Run         CW_Limit       [  TON  ]       CCW_Limit  CCW_Control.0
----] [--------+-----] [---------[IN    Q]---+------]/[----------( )-----
               |                 [       ]   |
               |                 [PT   ET]   |
               |                 [_______]   |
               |                             |
               |  CCW_Control.0              |
               +-----] [---------------------+

============================
Count all limit rising edges
- PV is twice the number of
  desired complete cycles
- CW_Limit is counted only
  after a CCW limit event
  has been counted
- First CCW_Limit is counted
  only after a CW_Limit
  event is detected
============================

  Auto_RunONS.Q                                                No_CCW_Limits_Yet
-------] [------------------------------------------------------------(S)-------

     CW_Limit                                                  No_CCW_Limits_Yet
-------] [------------------------------------------------------------(R)-------
                                                        ____________
                            _______                    [ CycleCount ]
      CW_Limit             [   >   ]                   [     CTU    ]  Auto_run
---+-----] [---------------[EN   o1]---+---------------[CU         Q]-----{R)---
   |                       [       ]   |               [            ]
   |         CycleCount.CV-[i1     ]   |  AutoRunONS.Q-[RESET     CV]
   |                       [       ]   |               [            ]
   |                     0-[i2     ]   |            2N-[PV          ]
   |                       [_______]   |               [____________]
   |                                   |
   |  CCW_Limit     No_CCW_Limits_Yet  |
   +-----] [---------------]/[---------+
 

Similar Topics

We have a quad monitor setup with FT SE and we are utilizing a header screen at the top of every display. when we open a new page we abort the...
Replies
0
Views
92
Our plant manger/my boss wants each line to display the takt time above the line. I am trying to research the cheapest way to do this. Our plant...
Replies
3
Views
185
Hi Folks, Looking for support and ideas. We are trying to go online to monitor/fault find on an S7-300 programmed with TIA Portal V15. I can...
Replies
9
Views
347
We finally replaced our Cognex Checker with an IV-3 and I'm wondering if can replace the s/w in the monitor with anything else. I haven't been...
Replies
0
Views
100
Im very new to programmin,but i was wanting to try and set up a program that could monitor the speed of a roller. Would it be possible to use the...
Replies
4
Views
155
Back
Top Bottom