This is part of an RSLogix 5000 Tutorial.
Someone recently said to me, “what we needed on that project was more planning and less complaining about what we couldn’t do.” Perhaps it’s fitting that we’re on step 9 out of 12 of this tutorial, and we’re only now about to write some actual logic. PLC programming, just like PC programming, is all about having a place for everything, and everything in its place. That takes planning.
When you write logic, you’re making a commitment that’s expensive to undo. Planning, structure and architecture are about building a strong foundation that’s going to support more than you initially intend to build on it. What I’ve presented in the tutorial up to this point is an architecture for your program that can grow with your program without becoming unwieldy and hard to maintain.
We already created a Faults program previously in the tutorial:
Start by adding an Aa_Main routine and setting it as the main routine of the Faults program like we did previously in the tutorial:
Faults are any condition that stops the machine from operating. It’s normal to break down you faults into different levels of severity:
You have to come up with your own severity levels based on your needs. These should correspond to how your machine will react when a fault occurs.
For instance, I normally differentiate between critical faults and normal faults like this: a critical fault requires the machine to stop immediately and a normal fault requires the machine to stop after completing the next cycle. It’s obviously better to stop at a nice stopping point, like a home or pounce position if you can, so you want more faults in the normal category than in the critical category.
Examples of critical faults:
- An emergency stop signal is received or guard door is opened
- Loss of communication to I/O modules
- Loss of encoder feedback signal (you can’t finish the cycle if you don’t know where you are)
Examples of normal faults:
- Sequence fault (expected part A but detected part B)
- Low air pressure signal
- Motor over-temperature signal
Which fault goes in which category is completely dependent on your application. If you have a very long cycle time, you may want to make an over-temperature fault into a critical fault because you can’t safely finish the cycle without causing damage.
Warnings generally don’t cause the machine to stop. In some sense they’re not faults at all, but we piggyback them on the fault system because we normally have an alarm system built on top of the fault system and we want warnings to show up as alarms on our operator interface. A warning might be something like “it’s time to change the oil filter”.
In addition to fault categories, you also need to consider fault zones. For instance if you’ve separated your machine into different zones correctly, then some faults should only affect one zone, not the whole machine. It’s quite normal for one fault to be a critical fault in one zone, but be a normal fault at the machine level. For instance, if you have an assembly line with 10 stations, losing the encoder feedback on an axis at station 3 might be a critical fault for the station 3 zone, but would only be a normal fault for the machine, which would allow the other 9 stations to finish their cycle. On the other hand, receiving and e-stop signal or losing the communication bus that services the entire assembly line are machine level critical faults that should immediately stop all stations.
We sometimes think of alarms as synonymous with faults, but that’s simply not true. A fault is an internal condition of the machine that may or may not cause various actions. An alarm is simply a message to the operator to notify them of an action they need to take. These are fundamentally different, but it’s also good practice to setup your faults so you can have a one-to-one mapping between faults and alarms. For instance, if you have a fault called “motor over-temperature”, you’ll likely want an alarm for this condition as well. However, I would suggest phrasing the alarm more like an instruction: “Motor Over-temperature Fault (number XXX), wait for 2 minutes, then reset the fault and restart the machine. If this fault occurs again, call maintenance.”
Now our washing machine example is a relatively simple machine. We don’t have much of an operator interface and the sequence is so simple and completely automatic that if something goes wrong there’s nothing the person using the machine can do except call a technician. Therefore we’re going to categorize everything as a critical fault, and there’s only one zone: the machine.
Create a second routine in the Faults program called Fa_Critical:
If you have more than just critical faults, you could add Fb_Normal and Fc_Warning routines, or whatever you need. The “F” prefix denotes faults in this case. You might have “F” routines in other sections of your code too, if you need to categorize your faults into fault zones. It’s best to implement that categorizing logic in the zones themselves.
Remember to open Aa_Main and add a JSR instruction to call the Fa_Critical routine:
Now open the Fa_Critical routine. For readability, it’s nice to start off the routine with a summary rung that creates a “no faults” bit. Now if you wanted to create an “any fault” bit, you’d have to OR together all the fault conditions with parallel branches, and that means the rung is going to get really tall in the vertical direction, meaning you’ll have to scroll more. The power of online PLC programming is the ability to monitor the status while it’s running. I prefer not to scroll unless absolutely necessary, and I like having as much information on the screen at once as I can. Therefore I prefer the following type of structure:
So the logic is: if not fault 1 and not fault 2 and not fault 3 … etc. … then I have no faults (or in this case, no critical faults).
Now you may be thinking, “why would you put each individual contact in that rung? Why not just put all your fault bits into DINT tags and then use EQU instructions to compare 32 bits at a time?” Well, then I’d say you haven’t been listening to anything I’ve been saying about readability. Your program will be written once and read many times, and nobody’s going to be reading it for a little light bedtime reading. They’ll be trying to decipher it when the machine is down and the company is losing hundreds or even thousands of dollars a minute and a plant manager is standing over their shoulder yelling at them to make them go faster. They’ll be sitting there counting bits in your DINT tag and cursing your name. Trust me on this. If I can impart one useful piece of knowledge to you about programming in general, it’s this: NEVER try to save time while writing a program if that time comes at the expense of readability. Learn to use tools like copy/paste/search/replace or export the code to an L5K format and write scripts to edit it there, but never sacrifice readability. That would be like a painter buying the most expensive paint and then throwing buckets of it on the wall because it’s faster than rolling it on.
We’re going to need a bunch of bits to use to store our faults. An array of DINT tags is a good idea. Decide how many you need (256 is a common “round” number but it’s going to depend on your application). I suggest creating the tag array (let’s say an array of 8 DINTs) but skipping bit 0. You can use it as your summary bit, or you can just ignore it, but it’s good to start numbering your faults at 1, and it helps with the readability if your bit numbers line up with your fault numbers. It’s quicker to just put a generic description (“FAULT 001”) on the first bit (array index 0, bit 1), and then use the import/export feature to export that tag to a file, open it in excel, and create descriptions for all 254 other faults, then import it back in. Then you’ll have all your bits numbered correctly in the tag descriptions.
At the same time, I suggest creating a matching array of timers, because it’s so common to need timers in your fault logic, and it’s nice if the timer number matches your fault number. Therefore, create an array of 256 TIMERs. I wouldn’t bother doing the export/import trick on the timers. Just give the array tag a comment like “FAULT TIMERS” and be done with it.
Creating Fault Logic
Now we can get down to business creating fault logic. During the “first pass” it’s a good idea to look through all your I/O and for each input or output ask yourself, “how would I know if the device hooked up to this input or output isn’t functioning correctly?” For instance, our first two inputs (LI01.0 and LI01.1) are “INLET Cold Water Valve On” and “INLET Cold Water Valve Off” respectively. This is a two position valve with two inputs to tell us the position of the valve. Since we have two inputs, we have an obvious consistency check we can do: we should either have one input or the other input on at a time, but never both, and the only time they should both be off is for a very short duration while the valve is changing between the on and off positions.
In our Fa_Critical routine, press Control-R to add a new rung:
Now let’s add the condition for when both of the inputs are on. Find the “normally open” contact on the toolbar above the editing window:
Click it twice to add two normally open contacts:
Click on the question mark above the first contact and type LI01.0 and then press enter. Then click on the question mark above the second contact and type LI01.1 and press enter. Since you already entered descriptions for these bits when you created the I/O mapping, the descriptions will show up above the contacts if it recognizes the tag:
That covers the first fault condition, when both inputs are on, but we also need to cover the condition when both inputs are off. We could add this condition on a separate fault, but I’m not going to get that fancy. But this means we need to add this second condition as a parallel branch. With the rung header selected (in the left margin where you see the number 1), click the branch icon in the toolbar:
That will give you a rather funny looking branch-around-nothing at the beginning of your rung:
Notice that the right hand side of the branch is highlighted. In order to get the branch to go around the two contacts you just created, click down on the highlighted side of the branch and drag it over to the right side of the two contacts. As soon as you start to drag, small gray rectangles will show up between and around the contacts, and these indicate valid places where you can attach your branch. When you drag it over near the small rectangle on the right side of the contacts, the rectangle will turn into a green oval:
That green oval means you’re close enough to let go of the mouse button and it will attach the branch to that spot. Go ahead and release the mouse button:
Now we want to insert two “normally closed” contacts, but if we did that with the right side of the branch highlighted, they would be inserted to the right of the branch. That’s not what we want. We want them inserted in the branch itself. First, click on the bottom left corner of the branch. It will highlight the corner like this:
Now find the “normally closed” contact on the toolbar and click it twice:
That will give you two new normally closed contacts on the branch:
Now select the question mark above the first normally closed contact and type LI01.0 and press enter, and then select the question mark above the second normally closed contact and type LI01.1 and press enter. You now have a completed branch:
Now when these conditions are true, we want to turn on our fault, but only after a short time delay (because we want to ignore the case where the valve is actually changing states). Therefore we need a timer. Start by selecting the right side of the branch:
Then we need to insert an “on delay timer”. The code for this timer is “TON” (tee-on). In the toolbar, you have to select the “Timer/Counter” tab, and then you can find the TON instruction icon:
Click the TON icon and you’ll see a new timer instruction inserted on the right side of your rung:
We’re going to need to create a new tag for this timer. In fact, we should create an array of tags for fault timers like I mentioned earlier. Start by right clicking on the question mark next to the word “Timer” in the new TON instruction, and click New Tag… from the context menu:
That will display a New Tag window. Let’s call the new array tag FC_TMR (meaning Faults – Critical – Timers). Put that in the name field. Enter “CRITICAL FAULT Timers” in the Description field. Now this tutorial is using a rather small example and I think we can get away with just 31 faults (bits 1 to 31 in a DINT tag), so let’s make this timer tag an array of length 32 (which will be indexes 0 to 31). Then we can use FC_TMR for fault 1, FC_TMR for fault 2, etc. To setup an array tag, change the Data Type field from TIMER to TIMER:
Click OK when you’re done. That created a new array tag, but it inserted just the name of the array into Timer field of the instruction:
We need to tell it to use timer number 1 from the array. Select where it says FC_TMR and type FC_TMR and press enter:
That corrected the Timer field, and it conveniently moved us to the Preset field. The Preset is the number of milliseconds that the timer will time before it turns on the DN (done) bit. In our case, enter 250 and press enter (that advances us to the Accumulator – just leave it at zero and press enter again):
Great. You’ll notice that since this is now a completely valid rung, the “e”‘s on the left have disappeared. Now we need to create our fault bit coil. When our fault conditions are true they start the timer and when the timer runs for 250 ms it will turn on the done bit (FC_TMR.DN). Use “Control-R” to create another rung, and then use the “normally open” and “coil” instruction icons in the toolbar to create a contact and a coil (remember you have to go back to the Favorites tab of the toolbar instead of the Timer/Counter tab):
You should have this:
Now select the question mark above the contact and type FC_TMR.DN:
Now for our fault bit. We’ll need a new tag to store the fault bit, and as I mentioned it’s a good idea to put them in DINT tags because it’s more efficient when you’re mapping those to some kind of user interface later (communications with DINT tags seems more efficient than reading one BOOL tag at a time). Right click on the question mark above the coil and click New Tag…:
That will pop up the New Tag dialog box. Let’s give our tag the name “FC” (Faults – Critical). Enter a Description: “CRITICAL FAULTS”. Then change the Data Type from BOOL to DINT:
Click OK. This creates the same issue we had with the timer array above – it inserted the new DINT tag as if it was a BOOL in the coil:
The coil needs a BOOL. Select where it says FC above the coil, then type FC.1 and press enter:
At least that got rid of the “e”‘s along the left side, so it’s a valid rung now. However, there’s still more work to be done. First of all, FC.1 needs a description (without one it just defaults to the description of the parent tag). To add a description for this particular fault, with the FC.1 text selected, press “Control-D”. That will open a description editor just above the logic window. Enter the description shown for the FC.1 coil:
To exit the description editor, either click the green checkmark, or click anywhere down in the ladder logic again.
Looking at our logic again, there’s a problem. When the condition that causes the fault is true for 250 ms it will turn on our fault bit, but as soon as the condition isn’t true anymore the fault will go away. There are a few cases where this is OK, but normally when a fault happens, we want the fault condition to “seal in” until the fault is reset. This prevents the machine from restarting until someone verifies that it’s OK. It also makes sure that an intermittent fault doesn’t keep stopping and starting the machine. In some cases stopping the machine can make a fault go away.
To “seal in” the FC.1 coil we need to create a branch around the FC_TMR.DN contact. You already know how to do this from above, so I won’t repeat it. Create a branch around the timer done bit, and then add a new normally open contact to that branch:
“Sealing in” means we’re going to use the coil itself as the condition in the branch. Here’s a shortcut to copy the name of the coil to the contact. Select where it says FC.1 above the coil then click and drag it over to the new contact:
You’ll know when you’re close enough because the green oval will show up next to the contact. Release the mouse button to drop:
There you go! Our first fully functional fault is done. Ok, but we’ve just created a fault that can never go away once it’s been tripped right? Once it’s sealed in, nothing can ever break the circuit, so it stays on forever. Normally we would put a normally closed contact in the branch as well with a fault reset signal of some kind that would let the operator reset the fault condition after they had checked to make sure the machine was OK. This is a washing machine though, and we don’t have a fault reset button. How do you reset the fault then? I’m taking advantage of a feature of the OTE (Coil) instruction in PLCs: whenever power is removed and then turned back on, the program does a “pre-scan” that sets the state of all Coils to “off”. Therefore if this washing machine faults, it stays faulted until the owner pulls the plug out for a few seconds and plugs it back in. It may not be the most user-friendly design, but without an operator interface or a fault reset button, it works…
So, one more step: let’s hook the new fault into our summary rung. Use the click-drag method to copy the FC.1 coil name up to the first normally closed contact in the summary rung. We might as well use FC.0 as the “No Faults” coil in the summary rung too:
Notice that I also added a Description to FC.0: “NO CRITICAL FAULTS”.
So, that’s fault #1 done! Go ahead and finish going through all of your inputs and outputs and creating faults that cover all of them. At the end of the tutorial I’ll provide an example of the completed program for you to compare against. In the next tutorial section we’ll be using the fault summary bit (FC.0) in our rungs that control the actual outputs.