One of the things I've found confusing when talking about State Machines is the difference between State and Step. Some people use the two terms interchangably, while others differentiate between State (Idle, Running, Paused), and Step ("Grip the Widget", "Weld Part A to Part B").
Some of it is just semantics (for instance, the above Steps can be considered to be part of the "Running" State, or rather, the Running State can be divided into other States such as "Running - Grip the Widget"; you would never have a "Idle - Grip the Widget"), but the differences can be important (For example, when coming out of the Paused State, it's nice to know what Step you are/were on so you can enter the Running State on the right Step. If you don't seperate the two concepts, then you may have to create lots more states: "Paused while Gripping", "Paused whil Welding", etc.)
I've only had a few clients who would allow SFCs (although there were many who wouldn't have cared - they didn't
intend for their maintenence folk to go into the PLC). Even so, I almost never use them.
SFCs are fine if everything goes according to plan. But if you add a PAUSE button, or an E-STOP, and they can get real ugly, real fast. That's the beauty of Ladder Logic - it is more adept at exception handling than SFCs and some of the other highter level hanguages.
But the
philosophy of SFCs, that's something that every novice PLC programmer should learn, to prevent the spagetti code that you've mentioned. The concept isn't hard - you have States (or Steps if you perfer), and the Transitions from one State to another State. That's all there is to it. The only thing to watch for is to make sure that the Actions that are being executed in a particular Step will generate the condition necessary to acheive the desired Transition.
I've seen two types of code that handle the Step/Transition logic well. The first type is of the "poke-a-number" variety. This includes the drum (e.g., the AB SQO instruction) or the equivalent Indirect Pointer to a bit matrix (which sounds like your counter)
The biggest problem with the counter types is getting the sequencer to recognize when there is a "Null" Step (or a step whose Transition conditions are met before the Step is active). Counters (and the SQO) need a Low-to-High transition, and so the rung conditions have to go back to Low (false) for at least one scan for the counter to recognise that it's OK to count again. I've seen some pretty fancy logic to accomplish this (successfully and consistantly), but it can be a problem.
The variant I use is the following code:
STEP 1 ON_STEP_1 TRANSIT_2 +-- MOV -+
-----| = |----+----| |---------| |------| 2 |
| | STEP |
| +--------+
|
| ON_STEP_1
+----------------------------( )
STEP 2 ON_STEP_2 TRANSIT_3 +-- MOV -+
-----| = |----+----| |----+----| |------| 3 |
| | | STEP |
| | +--------+
| |
| | TRANSIT_7 +-- MOV -+
| +----| |------| 7 |
| | STEP |
| +--------+
|
| ON_STEP_2
+----------------------------( )
`
Of course, this doesn't drive any outputs; this only controls the sequence. I have the ON_STEP_x coil on the above rung to do two things. First, it ensures that a sequence will be on a step for at least one scan. This helps prevent the opposite of the counter problem I mentioned above - blowing through a sequence too fast.
The other thing that the bit does is drive the outputs. A typical set of outputs might look like this:
ON_STEP_1 VALVE
---+----| |----+-----------( )
| |
| ON_STEP_2 |
+----| |----+
| |
| ON_STEP_3 |
+----| |----+
ON_STEP_2 PUMP
--------| |----------------( )
`
The exception logic can be put whereever its needed. I can put it as a condition to a Transition, a Step (IF E-STOP, MOVE to STEP Zero), as part of the output rung (stop the pump, but not closing the valve on a PAUSE condition).
The biggest limitation of the Poke-a-Number type of sequencers is that they don't handle diverging logic. If a sequence needs to spawn two simultaneous threads, and then later join back together, the Number sequncer fails, although an SFC can do this without much ado.
For these, I use the Latched-bit sequence. I know that the PLC world is divided very strongly into two camps regarding Latches and Unlatches. And there is much to be said in favor of both camps. The code that follows looks a lot like the badly-written code of a PLC programming newbie. BUT, if you truly understand the concepts of State and Transition, it works just like the above code:
ON_STEP_1 TRANSIT_2 ON_STEP_2
-----| |---------| |-------------+------(L}
|
| ON_STEP_1
+------(U)
ON_STEP_2 TRANSIT_3 ON_STEP_3
-------| |--------| |-------+----+-----(L)
| |
ON_STEP_5 TRANSIT_8 | | ON_STEP_2
-------| |--------| |-------+ +-----(U)
|
| ON_STEP_5
+-----(U)
`
Note that there is
one and only one rung to latch the bit which describes a Step. And that any Step which can lead to a particular Step gets unlatched on the same rung that that Step gets latched on. This violates the "rule" that some PLC programmers hold dear, that you must have only one unlatch and one latch for a bit.
TOUGH. It's a good little sequencer - easy to follow and troubleshoot. It drives outputs in the same manner that the first one does (I've seen one variation on this where the relevant outputs are Latched/Unlatch on each Step along with the Step bit - I don't recommend this, even if it does more closely resemble the philosophy of the SFC)
If the sequence needs to have two parallel sequences, then there would be two latches on the same rung. If the parallel sequences must join back together, both ON_STEP_x bits would be in series on the rung to latch the next next step (and unlatch both of those bits).