This is part of a series of articles that demonstrate how to create a runtime application for PLCnext Control. Each article builds on tasks that were completed in earlier articles, so it is recommended to follow the series in sequence.
Sample Runtime is a complete C++ project that implements a PLCnext runtime application. It utilises all the features used in previous articles in this series, and introduces the following new features:
-
Licence Manager RSC service - used to protect against the use of the application on unauthorised devices.
-
Subscription RSC service - used to receive notifications each time there is a change in the value of a subscribed GDS variables.
-
Axioline status register - used to monitor the status of the Axioline bus.
-
Enhanced real-time task management, including monitoring cycle time over-runs.
The source files for this application are located in the src directory of this repository.
Once you you have completed all parts of the series up to this point, you can build the Sample Runtime application as follows:
-
In your project's
srcdirectory, delete the fileRuntime.cpp. -
Copy all the source files for the Sample Runtime project into your project's
srcdirectory. -
Build the project to generate the
Runtimeexecutable.plcncli build
To run this example on a PLCnext Control device:
-
On a Windows 10 machine, download the PLCnext Engineer project from the PLCnEngTestProject directory.
This project includes an IEC 61131 project with GDS ports that are used by the the Sample Runtime.
-
In PLCnext Engineer, open the project, set the IP address of the PLC, and adjust the Axioline hardware configuration if necessary.
-
Download the PLCnext Engineer project to the PLC.
-
Copy the
Runtimeexecutable to the PLC.scp bin/AXCF2152_24.7.0.15/Release/Runtime admin@192.168.1.10:~/projects/RuntimeNote: If you receive a "Text file busy" message in response to this command, then the file is probably locked by the PLCnext Control. In this case, stop the plcnext process on the PLC with the command
sudo systemctl stop plcnextbefore copying the file.It is assumed that the ACF config and settings files are already on the PLC.
-
Open a secure shell session on the PLC:
ssh admin@192.168.1.10
-
Set the capabilities on the executable:
sudo setcap cap_net_bind_service,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_sys_boot,cap_sys_nice,cap_sys_time+ep projects/Runtime/Runtime
If the admin user does not have the privilege to run this command, then you will need to grant this privilege. One way to do this is to log in as root and add a file to the directory
/etc/sudoers.dcontaining this line:admin ALL=(ALL) /usr/sbin/setcap -
Restart the plcnext process:
sudo systemctl restart plcnext
-
Check the contents of the application log file:
cat /opt/plcnext/projects/Runtime/logs/Runtime.log
You should see messages containing I/O values being written to the log files 10 times each second.
-
Activate the fifth input on the digital input card.
You should see the sixth digital output come on.
The Sample Runtime application is now running.
Note that there is a shell script in the tools folder called start_debug.sh, which can be used as a template for automating the deployment, configuration and startup of the runtime application during project development.
The Sample Runtime application includes the following source files:
| File | Contents |
|---|---|
| PLCnextSampleRuntime.cpp | The main entry point for the application |
| CSampleRuntime.cpp / .h: | CSampleRuntime class |
| CSampleSubscriptionThread.cpp / .h: | CSampleSubscriptionThread class |
| CSampleRTThread.cpp / .h: | CSampleRTThread class |
| Utility.h: | Common definitions |
When the Sample Runtime application starts, the main function calls ArpSystemModule_Setup and then creates a single instance of CSampleRuntime.
The remaining sections describe the three classes used in this application. It is recommended that this be read alongside the corresponding source code.
This class is responsible for starting worker threads and handling PLC state changes. It contains one CSampleSubscriptionThread object, and one CSampleRTThread object.
When constructed, a CSampleRuntime object simply calls ArpPlcDomain_SetHandler to register the function named PlcOperationHandler. Subsequent operations are performed when the PLCnext Control calls PlcOperationHandler to signal a change of state:
-
Start Warm: On this event,
CSampleRuntimeperforms some initialisation, then tells theCSampleSubscriptionThreadobject and theCSampleRTThreadobject to start processing. -
Start Cold or Start Hot:
CSampleRuntimetells theCSampleSubscriptionThreadobject and theCSampleRTThreadobject to start processing. -
Stop, Reset or Unload:
CSampleRuntimetells theCSampleSubscriptionThreadobject and theCSampleRTThreadobject to stop processing.
When the PLC signals the "Start Warm" event, the following initialisation steps occur:
-
The
CSampleRuntimeobject subscribes to a number of RSC services:- Device Info service.
- Device Status service.
- Licence Status service.
-
Data is read from the Device Info and Device Status services:
- PLC vendor name
- Load on one CPU core
- Memory usage
- PLC board temperature
In this application, data is simply read during initialisation as a demonstration of how to use these services.
-
The Licence Status service is used to check that there is a valid licence for this application on this PLC. This mechanism can be used to protect against the use of the application on unauthorised devices. Currently, the only way to install an application licence on a PLC is by installing the application from the PLCnext Store.
In this example, the application cannot be instaled from PLCnext Store (because it doesn't exist there!), and so no valid licence will be found on the PLC. This application simply ignores the result of the licence check, but a real-world application can (for example) shut down or continue to operate in a "restricted" mode if no valid licence is found.
-
The
CSampleRTThreadobject is initialised. This involves the creation of two worker threads:- A real-time thread, which executes the
RTStaticCyclemember function. - A default priority thread, which executes the
StaticLoggingCyclemember function.
These two member functions are described below.
- A real-time thread, which executes the
-
The
CSampleSubscriptionThreadobject is initialised. This involves the creation of a single worker thread that executes theStaticCyclemember function. This member function is described below.
During initialisation (in the Init member function), the CSampleRTThread object creates a real-time thread for executing time-critical operations. It also creates a non-real-time thread to execute "slow" operations that, if run on the real-time thread, would affect the performance of the time-critical parts of the application.
When commanded to start processing (via the StartProcessing member function), the object:
-
Gets pointers to the Axioline Input and Output buffers in the Global Data Space.
-
Uses the
AddInputandAddOutputfunctions to populate twostd::maps with instances of theRAWIOstruct. This struct, defined inCSampleRTThread.h, contains all the data required to access a single Axioline I/O point, including the offset to the I/O data in the GDS buffer. -
Adds a special Axioline input variable called
AXIO_DIAG_STATUS_REG. This variables contains the current status of the Axioline bus, and can be used for diagnostics and error detection, e.g. to detect when an Axioline module has failed. Details of how to interpret values for this variable are given in the document "UM EN AXL F SYS DIAG", available for download from the Phoenix Contact website.Note that, since the structure of the Global Data Space is fixed during the startup of the PLCnext runtime, information about the location of I/O in the Global Data Space only needs to be obtained once, rather than every scan cycle. This provides a significant efficiency improvement over the way that I/O reads and writes were handled in the example shown earlier in this series.
Cyclic processing on the real-time thread is perfomed by the RTStaticCycle member function, which in turn calls the RTCycle member function. The main purpose of the RTCycle function is to schedule the start of the next scan cycle using the clock_nanosleep function. This provides a precise period for the processing of real-time operations. This function also checks for "real-time violations", i.e. any instances where the execution of the function takes longer than the specified cycle time.
During each scan cycle, the RTCycle function also calls these three functions:
ReadInputDataDoLogicWriteOutputData
DoLogic is the core of the real-time application, where process-specific logic is implemented. In this case, some basic binary operations are performed on a few digital inputs and outputs.
Cyclic processing on the non-real-time thread is performed by the StaticLoggingCycle member function, which in turn calls the LoggingCycle member function. This simply writes the current value of each I/O variable to the application log file, approximately every 100 milliseconds.
This class demonstrates the use of the "Subscription" RSC service for reading GDS variables. This service is an alternative to the "Data Access" RSC service, which can be used to "poll" the values of GDS variables. The Subscripton service, on the other hand, will notify the RSC client when the value of any subscribed GDS variables changes, eliminating the need for polling.
This application uses the Subscription service to read the value of three GDS variables. These variables have been declared in the PLCnext Engineer project that accompanies this example.
The Subscription service differs from most other RSC services, in that it employs delegates (also known as callback functions) to send notifications to subscribers.
In order to use the Subscription service:
-
Declare a shared pointer to store a service handle:
ISubscriptionService::Ptr m_pSubscriptionService;
-
Get the handle for an ISubscriptionService method:
CSampleRuntime::Init() { m_pSubscriptionService = ServiceManager::GetService<ISubscriptionService>(); //get ISubscriptionService }
-
Create a subscription for the port variables:
CreateSubscription() { m_uSubscriptionId = m_pSubscriptionService->CreateSubscription(SubscriptionKind::HighPerformance); m_pSubscriptionService->AddVariable(m_uSubscriptionId, GDSPort1) }
-
Read and assign port variables:
CSampleRuntime::ReadSubscription() { RSCReadVariableValues(m_zSubscriptionValues) m_zSubscriptionValues[nCount].CopyTo(m_gdsPort1) }
-
Delete the subscription:
CSampleRuntime::DeleteSubscription() { m_pSubscriptionService->DeleteSubscription(m_uSubscriptionId) }
You can get support in the forum of the PLCnext Community.
Copyright © 2020-2026 Phoenix Contact GmbH & Co. KG
All rights reserved. This program and the accompanying materials are made available under the terms of the MIT License which accompanies this distribution.