Contact and Coil | Nearly In Control

TAG | ladder-logic

Feb/16

20

How to Write a Big PLC Program

Staring down the barrel of a big automation programming project is intimidating. It’s hard to even know where to start. Even when you’ve done a few before, you’re only marginally more confident the next time.

I have quite a few big automation programming projects under my belt, so I think I can generalize the process a bit. Here goes:

1. Get the Prints

There’s almost no point in starting to program unless you have an almost final set of electrical drawings. If you don’t have them yet, push for them, and go do something else productive until you get them.

2. Create a Functional Specification

You don’t always have to write out a functional specification, but it at least needs to exist very clearly in your head. If at any point, you don’t know exactly how the machine is supposed to work in excruciating detail, stop writing code and go figure it out. Ask stakeholders, talk to operators, whatever it takes. Functional specifications are best written as a list of “user stories”. If you’re not sure what a functional spec should look like, check out Painless Functional Specifications by Joel Spolsky.

3. Shamelessly Copy

Identify what other projects you can find with logic that you can steal. Any code that works in another machine has the advantage of already being debugged. Don’t re-invent the wheel. (At the same time, never blindly copy logic without understanding it. Copying the code by re-typing it one rung at a time is still faster than writing it from scratch, and it’s a form of software review.)

4. Structure Your Project

Now you break open the ladder logic programming software and start creating your project. Pick your CPU type, setup the I/O cards based on the electrical drawings. Map your inputs. Plan out your program by creating programs or routines for each functional unit of the machine. Setup your fault summary rungs and your alarm logic.

5. Write the Manual Mode Logic

PLC logic is typically written “bottom up.” Manual mode logic is the lowest level of logic because it deals directly with individual functions in the machine. Advance cylinder. Retract cylinder. Home axis. Jog axis. While you’re writing the manual mode, this is when you take extreme care making sure that actions are interlocked so the machine can’t crash. If you’re using the Five Rung Pattern, this means paying attention to what goes in the Safe rung. Does cylinder A always have to advance before cylinder B can advance? The Safe rungs should reflect that. Make sure that even in manual mode, the operator (or you) can’t break the machine. Make sure to hook your faults and alarms into the applicable fault summary rungs and alarm logic.

6. Write Part Tracking Logic

Now that manual mode is complete, write the logic that tracks parts (and their state) through the machine. Remember, you should be able to run the machine in manual mode, and the part tracking should (ideally) work correctly. I know this isn’t always the case but surprisingly part tracking in manual mode can work 95% of the time. That means part tracking works based on the state of the machine. Closing the gripper with the robot in the pick position and the part present in fixture sensor on should latch a bit “remembering” that the gripper has a part in it.

Once you’ve written your part tracking logic, go back and use the part tracking and state bits to condition your Safe rungs. Don’t let the operator (or you) mistakenly open the gripper if the gripper has a part and isn’t in a safe position to let go of the part. Of course, you may need to add a way to manually override this (that’s what output forcing was created for), but in most cases you want to prevent improper operation.

Part of writing the part tracking logic is adding “ghost buster” screens. Operators often need to remove parts from a cell, and if the machine can’t detect their removal, then you have to provide the operator with a way to clear these “ghosts.”

At this point you’re actually ready to dump the program in and start testing out the machine electrically and mechanically. While it’s ideal to have a fairly complete program when you go onsite, we all know that’s not always entirely possible. At the very least you want to get to this point before startup begins.

7. Write the Auto Mode Logic

The complexity of your auto mode logic depends on what type of machine you’re programming. You’ll always need a cycle start and a cycle stop feature. Even if you’re in auto mode, you usually don’t want the machine to start until the operator specifically tells it to start. Once it’s running, we call this “in auto cycle.”

In simple machines, you can write the auto logic by filling in the Trigger rungs in your Five Rung logic. Start by putting the In Cycle contact at the beginning of the rung, and then writing logic after that which expresses when the action should take place. For instance, an advance reject part cylinder’s Trigger rung could be as simple as In Cycle, Part Present, and Part Rejected. As long as the Part Present tracking bit gets cleared once the cylinder is in the advanced position, then this is all the auto mode logic you need for this motion. Have the retract Trigger rung be In Cycle, No Part Present and Not Retracted.

More complicated machines need more complicated auto mode logic. If your machine has to perform a series of steps (even if some of them are in parallel) then consider using the Step Pattern. If your machine needs to choose between several possible courses of action (commonly seen in a storage and retrieval system) then consider using the Mission Pattern.

8. Review

It’s hard to write correct logic. Review your functional specification, point by point, and make sure your logic meets all of the requirements. Check your logic for errors. A fresh look often uncovers incorrect assumptions, typos, and outright mistakes. The earlier you find and fix problems, the easier they are to fix.

Make a list of everything you have to do during startup. Starting up a machine is time consuming and therefore expensive. Anything you can do to prepare saves you time and money.

Good luck, and keep your fingers out of the pinch points!


· ·

Jan/16

17

Start your own Automation Blog!

One thing I’ve discovered about automation blogging is that it’s a pretty lonely place. Don’t get me wrong, there are a couple gems out there, but I don’t find many people writing about what it’s like in the trenches wading through rungs of ladder logic. In the .NET world there are tons of programming blogs with posts about every issue you could ever come across. Why such a dearth of information in the automation space?

It occurs to me that blogging seems difficult to a lot of people, as if you need to be a web programmer. That’s totally untrue (I’m absolutely not a web programmer). In fact there are a ton of inexpensive and simple options out there.

Maybe people wonder what to write about. That’s easy. At first I worried about writing something people didn’t already know, but it turns out there’s no shortage of shiny new graduates looking for any toe-hold they can get in this industry. Try to think back to when you didn’t know anything. How did you figure out how to get online with a PLC the first time? Did you have to call the office to ask someone, and feel foolish because you didn’t know what a Null Modem cable was? We were used to asking our peers for help, but the new generation grew up looking up how to do things on the web.

Maybe you’re worried about the cost. Shared hosting plans are very inexpensive. I use DreamHost, specifically because they’re inexpensive (starts at $9 per month including domain name, unlimited storage and bandwidth), the hosting is rock solid, they offer one-click installs of blogging software (such as WordPress), and their technical support is excellent.

It would really be great if I didn’t feel like the only voice in this cloud. Grab a blogging account and chime in. Write about a hard problem and how you solved it. Disagree with me! Help me learn something new!

·

Nov/15

1

Offline Changes to a PLC Program

As a PLC programmer, you’ll often be asked to do a change to an existing system. If there’s a significant amount of functionality to be added, you generally get your changes ready “offline” and then do all the changes during a short window of time to minimize disruption to the production schedule.

If you’re using an Allen-Bradley PLC, the procedure is typically this:

  1. Get a copy of the latest program from the PLC (a.k.a. an “upload”)
  2. Make your changes to the offline copy, and write down every change you had to make
  3. Go online with the PLC and apply your changes as online changes

Step 3 is much safer than just taking your modified program and doing a “download”. That’s mainly because when you download, you’re not just downloading the program, but the memory state of the PLC as well. The PLC typically has to track things in memory (like recipe data, part tracking, data collection, sequence numbers, machine counters, etc.). If you do a download, you’re going to overwrite all those values with previous values, and that can cause a lot of problems. The other thing step 3 saves you from is simultaneous changes that were done online while you were busy making offline changes.

The only other option you have is upload-change-download, but you really have to shut the machine down for the duration to make sure that the internal state doesn’t change.

When I did a lot of Allen-Bradley programming, I didn’t question that. It’s just how it was. I remember visiting a plant one time for a service call, and the local maintenance person was a bit suspicious of what I was going to do (after all, I was a young kid who had never seen this machine before). He decided to quiz me a bit, and one of the things he asked was, “when you go online, do you download or upload?” I said “it depends,” but his answer was, “you never download.” I agreed that someone in a maintenance role should never need to do a download unless they’re replacing a CPU, or recovering from a corrupted PLC program.

Now that I mostly do Beckhoff TwinCAT 3 programming, I realized one of the benefits are that offline changes are a breeze. It’s due to the fact that TwinCAT 3 completely separates the program from the memory data. The program is stored in local files on your hard drive and compiled into a TMC file. The persistent data is stored in a different place on your hard drive.

When I want to do offline changes to a TwinCAT 3 project, here’s the procedure:

  1. Get a copy of the latest program
  2. Make your changes to the offline copy
  3. Copy changes back to the machine (keeping a backup, of course), rebuild, and activate configuration

This makes offline changes go a lot more smoothly, of course. I don’t have to copy and paste my changes in while online, so it takes less time and eliminates the possibility of a copy/paste error.

Since we also use Mercurial for version control, getting a copy of the latest program is a matter of pulling the latest from the source control, and copying it to the machine is a matter of pulling the offline changes to the machine. Any changes that were done in parallel can be merged with Mercurial’s built-in diff and merge utilities. (Note: I/O changes can’t be merged nicely, so if someone changed the I/O while you were doing your offline changes, you have to copy those changes in manually, but that’s rare and at least it tells you that it can’t merge them.)

This got me thinking that Allen-Bradley probably has a better way of doing offline changes that most of us just don’t know about. I know that you can do an upload without uploading the memory. However, it seems like it requires you to download both the program and data at the same time. I wonder if anyone out there knows how to do better offline changes to a ControlLogix. If so, I would be interested to know that.

· · · ·

You may have noticed I recently added a new section to this site: Patterns of Ladder Logic Programming. My goal, as usual, is to try to help new ladder logic programmers come up to speed faster and without all the trial and error I had to go through.

The new Patterns section is an attempt to distill ladder logic programs into their component parts. I assume the reader already knows the basic elements of ladder logic programming, such as contacts, coils, timers, counters, and one-shots. The patterns describe ways of combining these elements into larger patterns that you’re likely to see when you look through real programs. In my experience, you can program 80% of the machines out there by combining these patterns in applicable ways.

The Patterns section isn’t complete yet, but I will be adding to it slowly over time. If you think of a pattern that’s blatantly missing, please send me a note so I can include it.

·

Motion control is pretty complicated.

There’s been something really bothering me about the “integrated” motion control you find in PLCs these days (notably Allen-Bradley ControlLogix and Beckhoff TwinCAT). Don’t get me wrong, they’re certainly integrated far better than stand-alone motion controllers. Still, it just doesn’t “feel” right when you’re programming motion control from ladder logic.

When I’m programming a cylinder motion in ladder logic, I would typically use a five-rung logic block for each motion (extend/retract). One of the 5 bits is a “command” bit. This is a bit that means “do such-and-such motion now”. Importantly, if I turn that bit off, it means “stop now!” This works well for a cylinder with a valve controlling it because when I turn off power to that valve, the cylinder will stop trying to move. It would be nice if integrated motion was this simple.

It’s interesting to note that manual moves (a.k.a. “jogging”) are usually this simple. You drop a function block on a rung, give it a speed and direction, and when you execute it based on a push-button, the axis jogs in that direction, and when the push-button turns off, it stops jogging. Unfortunately none of the other features are that simple.

All other moves start motion with one function block and require you to stop it with another. The reason it works like this is because motion controllers also support blended moves. That is, I can first start a move to position (5,3) and after it’s moving there I can queue a second move to position (10,1) and it will guide the axes through a curved geometry that takes it arbitrarily close to my first point (based on parameters I give it) and then continue on to the second point without stopping. In fact you can program arbitrarily complex paths and the motion controller will perform them flawlessly. Unfortunately this means that 90% of the motion control logic out there is much more complex than it needs to be.

Aside: in object-oriented programming, such as in Java or .NET, it’s pretty normal to have to interface with a relational database such as MySQL or Microsoft SQL Server. However, when you try to mesh the two worlds of object-oriented programming and relational databases, you typically run into insidious little problems. Programmers call this the Object-relational impedance mismatch. I’m sure that if you added it up, literally billions of dollars have been spent trying to overcome these issues.

My point is that there is a similar Ladder logic-motion control impedance mismatch. The vast majority of PLC-based motion control is simple point-to-point motion. In that case, the ideal interface from ladder would be a single instance of a “go-to” function block with the following parameters:

  • Target Position (X, Y…)
  • Max Velocity
  • Acceleration
  • Deceleration
  • Acceleration Jerk
  • Deceleration Jerk

When the rung-in-condition goes true on this block, the motion control system moves to the target position with the given parameters, and when the rung-in-condition goes false, it stops. Furthermore, we should be able to change any of those parameters in real-time, and the motion controller should do its best to adjust the trajectory and dynamics to keep up. That would be all you need for most applications.

The remaining applications are cases where you need more complex geometries. Typically this is with multi-axis systems where you want to move through a series of intermediate points without stopping, or you want to follow a curved path through 2D or 3D-space. In my opinion, the ideal solution would be a combination of a path editor (where you use an editing tool to define a path, and it’s stored in an array of structures in the PLC) and a “follow path” function block with the following parameters:

  • Path
  • Path Tolerance

When the rung-in-condition is true, it moves forward along that path, and when it turns off, it stops. You could even add a BOOL parameter called Reverse which makes it go backwards along the path. The second parameter, “Path Tolerance” would limit how far off the path it can be before you get a motion error. I think this parameter is a good idea because it (a) allows you to initiate the instruction as long as your position is anywhere along that path, and (b) makes sure you’re not going to initiate some wild move as it tries to get to the first point.

A neat additional function block would be a way to calculate the nearest point on a path from a given position, so you could recover by jogging onto a path and continue on the path after a fault.

Obviously there needs to be a way for the PLC to generate or edit paths dynamically, but that’s hardly a big deal.

Anyway, these are my ideas. For now we’re stuck with this clunky way of writing motion control logic. Hopefully someone’s listening to us poor saps in the trenches! 🙂

· ·

Jul/14

1

Ladder Logic running on an Arduino UNO

Happy Canada Day!

Some of you may wonder if I’d fallen off the face of the Earth, but the truth is life just gets busy from time to time. Just for interest’s sake, here’s my latest fun project: an Arduino UNO running ladder logic!

Ladder Logic on a UNO

You may remember I wrote a ladder logic editor about 5 or so years ago called SoapBox Snap. It only had the ability to run the ladder logic in a “soft” runtime (on the PC itself). This is an upgrade for SoapBox Snap so that it can download the ladder logic to an Arduino and even do online debugging and force I/O:

Arduino UNO Ladder

I haven’t released the new version yet, but it’s very close (like a few days away probably).

Edit: I’ve now released it and here is a complete tutorial on programming an Arduino in Ladder Logic using SoapBox Snap.

· · ·

There’s a lot of stuff that falls under the term “functional programming,” but I’m just going to focus on the “functional” part right now, meaning when you define the value of something as a function of something else.

In ladder logic, we define the values of internal state (internal coils or registers) and outputs. We define these as functions of the inputs and internal state. We call each function a “rung”, and one rung might look like this:

Ladder diagram of Inputs A and B, and Internal State C

There’s something slightly odd going on in that rung though. You might say that we’ve defined C recursively, because C is a function of A, B, and itself. We all know, of course, that the PLC has no problem executing this code, and it executes as you would expect. That’s because the C on the right is not the same as the C on the left. The C on the right is the next state of C and the C on the left is the previous state of C.

Each time we scan, we redefine the value of C. That means C is an infinite time-series of true/false values. Huh?

Ok, imagine an array of true/false (boolean) values called “C”. The lower bound on the array index is zero, but the upper bound is infinite. C[0] is false (the value when we start the program). Then we start scan number 1, and we get to the rung above, and the PLC is really solving for is this:

Ladder logic defining C[1] as a function of A, B, and C[0]

If that were actually true (if it had an infinite array to store each coil’s value), then the ladder logic would be a truly functional programming language. But it’s not. Consider this:

Two ladder logic rungs with inputs A and B, internal coil C, and output D

In all modern PLCs, the first rung overwrites the value of C, so the second rung effectively uses the newly computed value for C when evaluating D. That means D[1] is defined as being equal to C[1] (the current state value of C). Why is this weird? Consider this:

Two previous rungs with the rung order reversed

By reversing the order of the rungs, I’ve changed the definition of D. After the re-ordering, D is now defined as C[0] (the previous state value of C) rather than C[1]. This isn’t a trivial difference. In an older PLC your scan time can be in the hundreds of milliseconds, so the D output can react noticeably slower in this case.

In a truly functional language, the re-ordering either wouldn’t be allowed (you can’t define D, which depends on C, before you define C) or the compiler would be able to determine the dependencies and re-order the evaluation so that C is evaluated before D. It would likely complain if it found a circular dependency between C and D, even though a PLC wouldn’t care about circular dependencies.

There are a few of reasons why PLCs are implemented like this. First, it saves memory. We would have to double our memory requirements if we always wanted to keep the last state and the next state around at the same time. Secondly, it’s easier to understand and troubleshoot. Not only does the PLC avoid keeping around two copies of each coil, but the programmer only has to worry about one value of each coil at any given point in the program. Third, the PLC runtime implementation is much simpler. It can be (and is) compiled to a kind of assembly language that can run efficiently on single threaded CPUs, which were the only CPUs available until recently.

Of course this comes with a trade-off. Imagine, for a moment, if rung-ordering didn’t matter. If you could solve the rungs in any order, that means you could also solve the rungs in parallel. That means if you upgraded to a dual-core CPU, you could instantly cut your scan time in half. Alas, the nature of ladder logic makes it very difficult to execute rungs in parallel.

On the other hand, we can still enforce a functional programming paradigm in our ladder logic programs if we follow these rules:

  • Never define a coil more than once in your program.
  • Don’t use a contact until after the rung where the associated coil has been defined.

That means there should only be one destructive write to any single memory location in your program. (It’s acceptable to use Set/Reset or a group of Move instructions that write to the same memory location as long as they’re on the same or adjacent rungs).

It also means that if coil C is defined on rung 5, then rungs 1 through 4 shouldn’t contain any contacts of coil C. This is the harder rule to follow. If you find you want to reference a coil before it’s defined, ask yourself if your logic couldn’t be re-organized to make it flow better.

Remember, someone trying to solve a problem in a PLC program starts at an output and uses cross references to move back through the program trying to understand it. Cross referencing from a contact to a coil that moves you forward in the program doesn’t require any logical leaps, but cross referencing to a coil later in the program means you need to logically think one scan backwards in time.

Benefits

While ladder logic isn’t a truly functional language, you can write ladder logic programs in the functional programming paradigm. If you do, you’ll find that your outputs react faster, and your programs are easier to understand and troubleshoot.

·

May/11

16

Ladder Logic vs. C#

PC programming and PLC programming are radically different paradigms. I know I’ve talked about this before, but I wanted to explore something that perplexes me… why do so many PC programmers hate ladder logic when they are first introduced to it? Ladder logic programmers don’t seem to have the same reaction when they’re introduced to a language like VB or C.

I mean, PC programmers really look down their noses at ladder logic. Here’s one typical quote:

Relay Ladder Logic is a fairly primitive langauge. Its hard to be as productive. Most PLC programmers don’t use subroutines; its almost as if the PLC world is one that time and software engineering forgot. You can do well by applying simple software engineering methods as a consequence, e.g., define interfaces between blocks of code, even if abstractly.

I’m sorry, but I don’t buy that. Ladder logic and, say C#, are designed for solving problems in two very different domains. In industrial automation, we prefer logic that’s easy to troubleshoot without taking down the system.

In the world of C#, troubleshooting is usually done in an offline environment.

My opinion is that Ladder Logic looks a lot like “polling” and every PC programmer knows that polling is bad, because it’s an inefficient use of processor power. PC programmers prefer event-driven programming, which is how all modern GUI frameworks react to user-initiated input. They want to see something that says, “when input A turns on, turn on output B”. If you’re familiar with control systems, your first reaction to that statement is, “sure, but what if B depends on inputs C, D, and E as well”? You’re right – it doesn’t scale, and that’s the first mistake most people make when starting with event-driven programming: they put all their logic in the event handlers (yeah, I did that too).

Still, there are lots of situations where ladder logic is so much more concise than say, C#, at implementing the same functionality, I just don’t buy all the hate directed at ladder logic. I decided to describe it with an example. Take this relatively simple ladder logic rung:

What would it take to implement the same logic in C#? You could say all you really need to write is D = ((A && B) || D) && C; but that’s not exactly true. When you’re writing an object oriented program, you have to follow the SOLID principles. We need to separate our concerns. Any experienced C# programmer will say that we need to encapsulate this logic in a class (let’s call it “DController” – things that contain business logic in C# applications are frequently called Controller or Manager). We also have to make sure that DController only depends on abstract interfaces. In this case, the logic depends on access to three inputs and one output. I’ve gone ahead and defined those interfaces:

    public interface IDiscreteInput
    {
        bool GetValue();
        event EventHandler InputChanged;
    }

    public interface IDiscreteOutput
    {
        void SetValue(bool value);
    }

Simple enough. Our controller needs to be able to get the value of an input, and be notified when any input changes. It needs to be able to change the value of the output.

In order to follow the D in the SOLID principles, we have to inject the dependencies into the DController class, so it has to look something like this:

    internal class DController
    {
        public DController(IDiscreteInput inputA, 
            IDiscreteInput inputB, IDiscreteInput inputC, 
            IDiscreteOutput outputD)
        {
        }
    }

That’s a nice little stub of a class. Now, as an experienced C# developer, I follow test-driven development, or TDD. Before I can write any actual logic, I have to write a test that fails. I break open my unit test suite, and write my first test:

        [TestMethod]
        public void Writes_initial_state_of_false_to_outputD_when_initial_inputs_are_all_false()
        {
            var mockInput = MockRepository.GenerateStub<IDiscreteInput>();
            mockInput.Expect(i => i.GetValue()).Return(false);
            var mockOutput = MockRepository.GenerateStrictMock<IDiscreteOutput>();
            mockOutput.Expect(o => o.SetValue(false));

            var test = new DController(mockInput, mockInput, mockInput, mockOutput);

            mockOutput.VerifyAllExpectations();
        }

Ok, so what’s going on here? First, I’m using a mocking framework called Rhino Mocks to generate “stub” and “mock” objects that implement the two dependency interfaces I defined earlier. This first test just checks that the first thing my class does when it starts up is to write a value to output D (in this case, false, because all the inputs are false). When I run my test it fails, because my DController class doesn’t actually call the SetValue method on my output object. That’s easy enough to remedy:

    internal class DController
    {
        public DController(IDiscreteInput inputA, IDiscreteInput inputB, 
            IDiscreteInput inputC, IDiscreteOutput outputD)
        {
            if (outputD == null) throw new ArgumentOutOfRangeException("outputD");
            outputD.SetValue(false);
        }
    }

That’s the simplest logic I can write to make the test pass. I always set the value of the output to false when I start up. Since I’m calling a method on a dependency, I also have to include a guard clause in there to check for null, or else my tools like ReSharper might start complaining at me.

Now that my tests pass, I need to add some more tests. My second test validates when my output should turn on (only when all three inputs are on). In order to write this test, I had to write a helper class called MockDiscreteInputPatternGenerator. I won’t go into the details of that class, but I’ll just say it’s over 100 lines long, just so that I can write a reasonably fluent test:

        [TestMethod]
        public void Inputs_A_B_C_must_all_be_true_for_D_to_turn_on()
        {
            MockDiscreteInput inputA;
            MockDiscreteInput inputB;
            MockDiscreteInput inputC;
            MockDiscreteOutput outputD;

            var tester = new MockDiscreteInputPatternGenerator()
                .InitialCondition(out inputA, false)
                .InitialCondition(out inputB, false)
                .InitialCondition(out inputC, false)
                .CreateSimulatedOutput(out outputD)
                .AssertThat(outputD).ShouldBe(false)

                .Then(inputA).TurnsOn()
                .AssertThat(outputD).ShouldBe(false)

                .Then(inputB).TurnsOn()
                .AssertThat(outputD).ShouldBe(false)

                .Then(inputA).TurnsOff()
                .AssertThat(outputD).ShouldBe(false)

                .Then(inputC).TurnsOn()
                .AssertThat(outputD).ShouldBe(false)

                .Then(inputB).TurnsOff()
                .AssertThat(outputD).ShouldBe(false)

                .Then(inputA).TurnsOn()
                .AssertThat(outputD).ShouldBe(false)

                .Then(inputB).TurnsOn()
                .AssertThat(outputD).ShouldBe(true); // finally turns on

            var test = new DController(inputA, inputB, inputC, outputD);

            tester.Execute();
        }

What this does is cycle through all the combinations of inputs that don’t cause the output to turn on, and then I finally turn them all on, and verify that it did turn on in that last case.

I’ll spare you the other two tests. One check that the output initializes to on when all the inputs are on initially, and the last test checks the conditions that turn the output off (only C turning off, with A and B having no effect). In order to get all of these tests to pass, here’s my final version of the DController class:

    internal class DController
    {
        private readonly IDiscreteInput inputA;
        private readonly IDiscreteInput inputB;
        private readonly IDiscreteInput inputC;
        private readonly IDiscreteOutput outputD;

        private bool D; // holds last state of output D

        public DController(IDiscreteInput inputA, IDiscreteInput inputB, 
            IDiscreteInput inputC, IDiscreteOutput outputD)
        {
            if (inputA == null) throw new ArgumentOutOfRangeException("inputA");
            if (inputB == null) throw new ArgumentOutOfRangeException("inputB");
            if (inputC == null) throw new ArgumentOutOfRangeException("inputC");
            if (outputD == null) throw new ArgumentOutOfRangeException("outputD");

            this.inputA = inputA;
            this.inputB = inputB;
            this.inputC = inputC;
            this.outputD = outputD;

            inputA.InputChanged += new EventHandler((s, e) => setOutputDValue());
            inputB.InputChanged += new EventHandler((s, e) => setOutputDValue());
            inputC.InputChanged += new EventHandler((s, e) => setOutputDValue());

            setOutputDValue();
        }

        private void setOutputDValue()
        {
            bool A = inputA.GetValue();
            bool B = inputB.GetValue();
            bool C = inputC.GetValue();

            bool newValue = ((A && B) || D) && C;
            outputD.SetValue(newValue);
            D = newValue;
        }
    }

So if you’re just counting the DController class itself, that’s approaching 40 lines of code, and the only really important line is this:

    bool newValue = ((A && B) || D) && C;

It’s true that as you wrote more logic, you’d refactor more and more repetitive code out of the Controller classes, but ultimately most of the overhead never really goes away. The best you’re going to do is develop some kind of domain specific language which might look like this:

    var dController = new OutputControllerFor(outputD)
        .WithInputs(inputA, inputB, inputC)
        .DefinedAs((A, B, C, D) => ((A && B) || D) && C);

…or maybe…

    var dController = new OutputControllerFor(outputD)
        .WithInputs(inputA, inputB, inputC)
        .TurnsOnWhen((A, B, C) => A && B && C)
        .StaysOnWhile((A, B, C) => C);

…and how is that any better than the original ladder logic? That’s not even getting into the fact that you wouldn’t be able to use breakpoints in C# when doing online troubleshooting. This code would be a real pain to troubleshoot if the sensor connected to inputA was becoming flaky. With ladder logic, you can just glance at it and see the current values of A, B, C, and D.

Testing: the C# code is complex enough that it needs tests to prove that it works right, but the ladder logic is so simple, so declarative, that it’s obvious to any Controls Engineer or Electrician exactly what it does: turn on when A, B, and C are all on, and then stay on until C turns off. It doesn’t need a test!

Time-wise: it took me about a minute to get the ladder editor open and write that ladder logic, but about an hour to put together this C# example in Visual Studio.

So, I think when someone gets a hate on for ladder logic, it just has to be fear. Ladder logic is a great tool to have in your toolbox. Certainly don’t use ladder logic to write an ERP system, but do use it for discrete control.

·

I just realized something I didn’t learn until at least a year into programming PLCs, and thought it would be a great thing to share for newer ladder logic programmers: when should you use a sealed-in coil vs. a latch/unlatch?

On the surface of it, a latch/unlatch instruction is sometimes frowned upon by experienced programmers because it’s correlated with bad programming form: that is, modifying program state in more than one location in the program. If you have one memory bit that you’re latching and unlatching all over the place, it really hinders readability, and I pity the fool that has to troubleshoot that code. Of course, most PLCs let you use the same memory bit in a coil instruction as much as you want, and that’s equally bad form, so I don’t take too strict of a stance on this. If you are going to use latch/unlatch instructions, make sure you only use one of each (for a given memory bit), and keep them very close together (preferably on adjacent rungs, or even in different branches of the same rung). Don’t make the user scroll, or worse yet, do a cross reference.

As you can imagine, if you’re going to use a Latch/Unlatch instruction and keep them very close together, it’s trivial to convert that to a rung with a sealed in coil, so what, if anything is the difference? Why have two sets of instructions that do the same thing?

It turns out (depending on the PLC hardware you’re using) that they act differently. On Allen-Bradley hardware, at least, an OTE instruction (coil) will always be reset (cleared to off) during the pre-scan. The pre-scan happens any time you restart the program, which is most importantly after a loss of power. If you’re using a sealed in coil to remember you have a pallet present in a zone, you’ll be in for a big surprise when you cycle power. All your zones will be unblocked, and you could end up with a bunch of crashes! On the other hand, OTL and OTU instructions don’t do anything during a pre-scan, so the state remains the same as it was before the power was removed.

For that reason, a latch/unlatch is a great indication of long term program state. If you have to track physical state about the real world, use a latch/unlatch instruction.

On the other hand, a sealed-in coil is a great way to implement a motion command (e.g. “attempting to advance axis A”). In that case you want your motion command to reset if the power drops out.

I hope that clears it up a bit. I always tried to avoid all latch/unlatch instructions until I understood these concepts.


·

Sep/10

28

Clean Ladder Logic

I’ve recently been reading Clean Code: A Handbook of Agile Software Craftsmanship. It’s written by Robert C. “Uncle Bob” Martin of Agile software (among other) fame. The profession of computer programming sometimes struggles to be taken seriously as a profession, but programmers like Martin are true professionals. They’re dedicated to improving their craft and sharing their knowledge with others.

The book is all about traditional PC programming, but I always wonder how these same concepts could apply to my other obsession, ladder logic. I’m the first to admit that you don’t write ladder logic the same way you write PC programs. Still, the concepts always stem from a desire for Readability.

Martin takes many hard-lined opinions about programming, but I think he’d be the first to admit that his opinions are made to fit the tools of the time, and those same hard-and-fast rules are meant to be bent as technology marches on. For instance, while he admits that maintaining a change log at the top of every source file might have made sense “in the 60’s”, the rise of powerful source control systems makes this obsolete. The source control system will remember every change that was made, who made it, and when. Similarly, he advocates short functions, long descriptive names, and suggests frequently changing the names of things to fit since modern development environments make it so easy to rename and refactor your code.

My favorite gem is when Martin boldly states that code comments, while sometimes necessary, are actually a failure to express ourselves adequately in code. Sometimes this is a lack of expressiveness in the language, but more often laziness (or pressure to cut corners) is the culprit.

What would ladder logic look like if it was “clean”? I’ve been visiting this question during the development of SoapBox Snap. For instance, I think manually managing memory, tags, or symbols is a relic of older under-powered PLC technology. When you drop a coil on the page in SoapBox Snap, you don’t have to define a tag. The coil is the signal. Not only is it easier to write, it prevents one of the most common cardinal sins of beginner ladder logic programming: using a bit address in two coil instructions.

Likewise, SoapBox Snap places few if any restrictions on what you can name your coils. You don’t have to call it MTR1_Start – just call it Motor 1: Start. Neither do you need to explicitly manage the scope of your signals. SoapBox Snap knows where they are. If you drop a contact on a page and reference a coil on the same page, it just shows the name of the coil, but if you reference a contact on another page, it shows the “full name” of the other coil, including the folders and page names of your organization structure to find it. Non-local signals are obviously not local, but you still don’t have to go through any extraneous mapping procedure to hook them up.

While we’re on the topic of mapping, if you’ve read my RSLogix 5000 Tutorial then you know I spend a lot of time talking about mapping your inputs and your outputs. This is because RSLogix 5000 I/O isn’t synchronous. I think it’s pointless to make the programmer worry about such pointless details, so SoapBox Snap uses a synchronous I/O scan, just like the old days. It scans the inputs, it solves the logic, and then it scans the outputs. Your inputs won’t change in the middle of the logic scan. To me, fewer surprises is clean.

I’ve gone a long way to make sure there are fewer surprises for someone reading a ladder logic program in SoapBox Snap. In some ladder logic systems, the runtime only executes one logic file, and that logic file has to “call” the other files. If you wanted to write a readable program, you generally wanted all of your logic files to execute in the same order that they were listed in the program. Unfortunately on a platform like RSLogix 5000, the editor sorts them alphabetically, and to add insult to injury, it won’t let you start a routine name with a number, so you usually end up with routine names like A01_Main, A02_HMI, etc. If someone forgets to call a routine or changes the order that they execute in the main routine, unexpected problems can surface. SoapBox Snap doesn’t have a “jump to page” or “jump to routine” instruction. It executes all logic in the order it appears in your application and each routine is executed exactly once per scan. You can name the logic pages anything you want, including using spaces, and you can re-order them with a simple drag & drop.

Program organization plays a big role in readability, so SoapBox Snap lets you organize your logic pages into a hierarchy of folders, and it doesn’t limit the depth of this folder structure. Folders can contain folders, and so on. Folder names are also lenient. You can use spaces or special characters.

SoapBox Snap is really a place to try out some of these ideas. It’s open source. I really hope some of these innovative features find their way into industrial automation platforms too. Just think how much faster you could find your way around a new program if you knew there were no duplicated coil addresses, all the logic was always being executed, and it’s always being executed in the order shown in the tree on the left. The productivity improvements are tangible.

· · · ·

Older posts >>

Theme Design by devolux.nh2.me