1

My main language is embedded C/C++, but I have a little background in structured text and worked with Codesys a while back in school. I am still figuring out what is possible and best practice in the Codesys environment.

Currently, I am working on a project with a controller PLC that will be controlling a few other devices over CAN bus. I've written the programs to communicate and control the different devices, which I call modules. I call on the modules with my main PLC program (PLC_PRG).

As an example, I added a simplified version of the software structure below.

Simplified software structure

Now, every device that I'm communicating with has a different cycle time that it wants is communication in. Because of that, I currently have given every module their own task with their own cycle time and priority.

For the PLC_PRG to interact, I use public methods and properties. I really like this functionality and would like to keep using it this way, but I have stumbled on a structural question that made me wonder what would be better practice.

There is a possibility I'll have two or more of one device that all need to be communicated with individually. This would mean that I'd need to be able to run multiple instances of the module.

As far as I can figure out, it is not possible to run multiple instances of a program as tasks. It is also not possible to make a function block a task as it needs to be initiated as an object in a program to be used.

There are different ways in which I could rewrite the modules as function blocks and run them at timed intervals from the main plc_prg. But I don't like the visualisation of that solution and really like the way task management works in Codesys. So what would be the best practice of having multiple instances of function blocks that need to run on a certain cycle time different from the main PLC_PRG?

P.S. While I was writing this, I had a thought that I could split up the module in a function block and an interface program in which I then could create the different instances and could be called as a task. Then PLC_PRG would interact with the interface program. It still feels like a little crude of a solution, though.

A quick example of the idea:

Last idea for a solution

1
  • 1
    You can certainly have multiple instances of a program within a task, however how would the individual instances know which device it is communicating to? You would end up passing variables between PLC_Prg and the instances with IF statements to handle the different devices, AKA spaghetti code. Typically a device would be represented by a FB and the FB for each device would be instantiated within the owning tasks program. If the devices require a specific cycle time (which would be unusual, and should not be relied upon as precise) then change the cycle time of the instantiating task program. Commented Jun 17 at 9:04

2 Answers 2

2

The simplest solution, as Fred mentioned, would be to unify it all into a single program for modules and handle the different module cycles using simple TON timers, so something like this:

VAR CONSTANT
    deviceXCycle: TIME := T#500MS;
    deviceYCycle: TIME := T#1S;
END_VAR
VAR
    deviceX: Module1((* initialization *));
    deviceXTimer: TON;

    deviceY: Module1((* initialization *));
    deviceYTimer: TON;
END_VAR

deviceXTimer(IN := NOT deviceXTimer.Q, PT := deviceXCycle);
IF deviceXTimer.Q THEN
    deviceX();
END_IF

deviceYTimer(IN := NOT deviceYTimer.Q, PT := deviceYCycle);
IF deviceYTimer.Q THEN
    deviceY();
END_IF

You could even abstract the timer part by integrating it into the module itself and passing the cycle time in the FB_Init constructor.

The other options is to do what you suggested at the end, have different programs running with tasks of different cycles, each handling the devices that need to work in that cycle time, and give the programs DeviceX and DeviceY properties of type REFERENCE TO Module1 to have direct access to the actual modules.

But this isn't very ergonomic, for example, if the cycle time needs to change for one device in the future, with the above approach you just update one TIME constant variable, but with this approach you have to refactor quite a bit of code.

In my opinion, and again, this is just my opinion, it's often best to KISS (Keep It Stupid Simple).

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you, I'm really trying to understand. In the current set up I'm keeping the devices 100% seperate . Every device has their own task and I only need to change the cycle time of that task if needed. So the only downside currently is that I can't make multiple instances of the same program to add to the corresponding task. What I don't understand currently is why using timers is more straight forward then using the task functionality. I thought tasks were a good visualization when coming into the project and timers would make my main program code longer/less clear.
It really depends on preference. As others mentioned, device communications are usually handled using FBs, which would allow you to easily instantiate many copies, handle all of them in a single place, put similar ones in an array and handle them as a group and etc. Timers would allow for a finer control, such as on demand trigger by the operator, for example, for a device that is polled every minute (and yes, I have used such), you can have a button in the HMI to trigger the poll, and easy runtime configuration of the cycle time (again, in the HMI, assuming the time variable isn't a constant)
1

I am always hesitant to make broad pronouncement on other people's designs, because there always is a point where you hit the limit of you own knowledge of both the technology and obviously other people's specific applications. I would rather provide ideas for consideration rather than outright advice.

It is typical to have dedicated tasks assigned to process fieldbusses, but that does not mean that the function blocks accessing inputs and outputs need to live inside separate tasks too.

When possible, to preserve the robustness of control logic which may need to survive generations of hardware and changing fieldbusses, I try to abstract away quasi-hardware considerations like cycle time, rather than use them as the main criteria for modularization.

Have you considered the blunt approach of simply having your whole logic run at a single cycle time, the fastest you think your need (or faster!), thereby avoiding the architectural complication of splitting modules by cycle time? It may turn out the load on your PLC is very low to start with, and doing that moves the needle from very low to just a little less very low. By contrast, your time is a precious and very limited resource, and a simpler architecture saves your time (quicker to develop, fewer bugs, easier to maintain, etc.).

2 Comments

Thank you for your feedback. The different cycle times aren't an option. I didn't mention it originially, because I thought it not relevant, but the communication is going over CANbus. Devices tend to ask for continuous communication and with different intervals. I changed it in the explanation
Control logic tasks and I/O tasks need not have the same cycle time. For instance, I may have Profinet isolated in a task with a 4ms cycle, EtherCAT in another task with its own cycle, and my main logic (which uses I/O from all busses) running at, say, 250 microseconds. I do not have experience with CANbus, but everything I have worked with does not require control logic to run at the same cycle.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.