The perils of FB_init and others
When creating a new function block (FB) some sort of initialization may be needed which is more complicated than simple variable allocation. Simple variable allocation can be done directly in the declaration as documented by Beckhoff in Declaring Variables. If the use case is more complicated and requires control flow or calling a function it needs to be moved to the main body of the FB or one of its methods. There are two already defined ways of doing this more complicated initialization. The
FB_init method or the
Call After Init pragma. Using these can help clean up the main FB body definition by moving call only once code to a specific method that only gets called once. However, this can lead to issues if trying to access information that is not yet available.
There isn't much information available when it comes to these, the following is what I was able to piece together. If you have extra information or corrections, please let me know.
There are three different contexts: Windows Context, Real Time Context, and Cyclic Context.
The Windows Context is not real time. This is were the OS, applications, HMI, acyclic communication take place. ADS based FBs interacting with the windows host. This would be done on the shared portion of RT core that are shared or non RT cores.
The next two contexts are real time. Generally, PLC code will be executed in the Cyclic Context. When the TwinCAT is in run mode and your PLC is running, it is running in this Cyclic Context. This is where EtherCAT telegrams are controlled and scheduled and by extension IO. This Cyclic Context are the Tasks defined in TwinCAT and each will have individual specific parameters like task cycle time.
What about the Real Time, but no cyclic context, it is active for a short period of time. This happens when the TwinCAT system is switched between its Run and Config modes. This is part of the boot strapping process to control the cyclic tasks. This is where the code inside Initialization Methods:
Calling functions in the Real Time Context that are returning Cyclic Context information will not create errors, but will return undefined or incorrect results. This is arguably worse, as it is harder to debug. Want to access information about the underlying system, task, IO, etc. Where does this information live? I have yet to find an something tha tis available in the Real Time Context and not the Cyclic Context. A better question to ask, is this also available in the Real Time Context as well as the Cyclic Context?
There is one function which explicitly defines its different behavior in different contexts. The GETCURTASKINDEXEX function. Its more naive little brother GETCURTASKINDEX, does not provide this context information in its return value. Based on my testing, it will not fail or error, but always return the value of 1. This works great if your PLC project only has one task, but can lead to less than ideal behavior with more than one PLC task.
This is illustrated below.
fb_idx uses the
FB_init method and
fb_idx_after uses the
call_after_init pragma. Each one uses both
GETCURTASKINDEXEX. They set
idxBody in their respective init method and the body of the FB. The project is configured with two separate tasks.
This shows that retrieving the task index must be used inside the Cyclic Context and cannot be done during the initialization phase.
Next up there is the F_GetMappingStatus function. It checks whether a variable is linked to a physical device. Great to use for automatic simulation and fallback or hiding items in a template. This is a function that you do not want to call cyclicly given it is fairly expensive. It is therefore perfect to be called as part of the initialization phase. However, if called outside the Cyclic Context it will always return
EPlcMappingStatus.MS_Unmapped, regardless of actual mapped status. This is undesirable and therefore it must be called inside a regular task cycle. You can either use the PlcTaskSystemInfo.FirstCycle, or have it part of your initialization state inside your FB body.
These setup and teardown methods can be very useful in specific instances. They do, however, add a level of complexity for debugging and when doing anything more than simple logic and assignments, ensure whatever you are trying to access is available. I'm sure there are more functions and FBs in Beckhoff libraries that will give similar results.
As mentioned previously, this is partly based on my testing and assumptions given the limited documentation from Beckhoff on this subject. If you have any corrections, or further information, please get in touch with me.
Download the project.