Modular Programming: Building Your PLC Programs Like LEGO

In industrial automation programming, have you ever faced the dilemma of increasingly lengthy programs, with logic becoming more complex, making debugging feel like navigating a maze? When devices need modifications or functionalities need to be expanded, a small change can trigger unpredictable issues?

After years of practice and reflection, I have found an efficient solution—modular programming. This is not just a technical method but a shift in programming mindset.

Step 1: Create Dedicated Function Blocks—Your Program’s “Building Blocks”

Your task is to create a dedicated function block for each type of “building block”.

For example, for a standard double-control cylinder, you encapsulate a function block, which I like to call FB_Cylinder. This block encompasses all its “private matters”: automatic operation, manual forward/reverse, timeout alarms, sensor anomaly detection… When the main program needs to call it, it only needs to provide a few simple instructions: start, reset, manual mode selection. As for how it operates through solenoids or how it determines sensor status, the main program does not inquire.

The most direct benefit of this approach? The next time you encounter a double-control cylinder, you can simply plug this “LEGO” block in, fill in the actual IO addresses and parameters, and it comes to life. You no longer need to rewrite the logic; you are simply “reusing”.

This reuse mindset brings astonishing efficiency improvements. Imagine a medium-sized project with dozens of cylinders, motors, and valves; if each one had to be programmed from scratch, not only would the workload be immense, but errors would also be likely. By reusing function blocks, you are effectively building your own library of standard components.

Step 2: Establish a Clear Program Hierarchy, Write Programs Like Building a House

Once the modules are dismantled, they cannot be haphazardly piled together. A solid house requires a load-bearing structure, and your program is no different. I personally advocate a three-layer structure: main program layer, device control layer, and execution layer.

The main program layer is the brain, responsible for high-level scheduling and mode management. For instance, it handles automatic/manual/pause modes, manages overall alarms, and calls the device control layer. It does not directly drive any devices.

The device control layer acts as the central nervous system. It translates the main program’s instructions into specific device actions. For example, a “feeding” process will call a series of function blocks such as “conveyor FB”, “lifting cylinder FB”, “clamping cylinder FB”, and coordinate their action sequences.

The lowest layer is the execution layer, which consists of the function blocks we created in the first step. They are only responsible for executing commands, diligently completing their tasks without concern for the complex logic upstream.

Once this structure is established, your program instantly becomes clearer. During debugging, you can have an overview at the main program layer; when troubleshooting device failures, you can dive directly into the corresponding function block. It feels like organizing a cluttered tool room into a precisely labeled toolbox.

Step 3: Define Your “Communication Language” for Efficient Module Interaction

Modules cannot be deaf and mute; they need to communicate. But communication cannot rely on shouting; there must be a unified protocol. This is the importance of interface standardization.

In my framework, each function block has strictly defined input and output parameters.

The benefit of this is that when you connect a UDT variable to an HMI, all structure members are automatically generated, saving time and reducing errors.

Function blocks interact through interfaces: For instance, the function block for conveyor A needs a signal indicating that “the next station is ready”; this signal should be passed as an input parameter to conveyor A’s function block, rather than having conveyor A’s function block search for it in global variables.

Naming! Naming! Naming! Important things should be said three times. Don’t use “DB1.DBX0.0”; use “Stn1_Motor_Run”. A good naming convention is the best gift you can give to your future self (and your colleagues who will take over). My habit is “station_device_function”, which is clear at a glance.

A unified interface specification not only improves code readability but also lays the foundation for team collaboration. When everyone follows the same standards, code handover and maintenance become exceptionally simple.

Step 4: Make Good Use of UDTs and Arrays for Batch Processing

In addition to encapsulating individual function blocks, we can further optimize program structure through UDTs (User Defined Types) and arrays. For multiple devices of the same type, such as several roller motors on a conveyor line, you can:

1. Create a UDT for the motors, containing all operational parameters and statuses.

2. Create a motor function block that accepts this UDT as a parameter.

3. Declare an array of motor UDTs in the program.

4. Use a loop to call the same function block to handle all motors.

This method not only reduces the amount of code but, more importantly, ensures consistency in behavior among devices. When you need to modify a parameter for all motors, you only need to change it in one place instead of searching through each one.

Step 5: Implementation and Iteration—Let the Framework Grow in Projects

The framework is not an artwork placed in a glass cabinet; it must be used in projects and continuously refined. I have two very practical suggestions for you:

1. Start with a “seed project”. Do not attempt to create a perfect framework in your first project. Choose a project of moderate scale with a relaxed timeline as your “testing ground” for modular thinking. Treat the program of this project as your first “framework prototype”.

2. Establish your “Programming Specification Manual”. This manual does not need to be complex; a one or two-page Word document will suffice. It should outline: how many layers does our project use? What are the naming rules for function blocks? How should variables be named? How are UDTs defined? Before starting a new project, spend ten minutes reviewing this manual with team members (even if it’s just you). This simple action can save you about 30% of debugging and communication time later.

Last year, I used this method to guide a newcomer. At first, he found it cumbersome. But after completing a project, he independently handled a small modification and came back to tell me: “Boss, following the framework and manual, I hardly asked you any questions, and it worked perfectly on the first power-up.” At that moment, I felt that all the initial “struggles” were worth it.

Conclusion: From Knowing to Doing

Alright, the five steps are complete. But to be honest, reading ten articles is not as effective as doing it once. I suggest you open an old project you are familiar with and try to reconstruct it in your mind or create a new program using the ideas discussed today.

You will find that what hinders you is no longer technology, but habits. Once you break through that habit threshold, you will never go back.

Modular programming is not just a technique; it is a way of thinking. It requires us to step out of the limitations of individual devices and think about problems from a system perspective, building maintainable, scalable, and reliable automation systems. The path may start off a bit challenging, but once you embark on it, you will find the road ahead widening.

What is your biggest confusion when organizing programs? Is it unclear layering or difficulty in module division? Feel free to discuss in the comments.

Leave a Comment