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:
- The valve is at the CW limit:
- The valve is at the CCW limit:
- 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.