A Hands-On Introduction to OPC UA: Turning a Raspberry Pi 5 into a PLC
Monitor and Write Variables—and Control an LED—with CODESYS, UaExpert, and GPIO

OPC UA is often described as an “industrial communication standard” or a “secure information model.” Those descriptions are accurate, but they can still feel a little abstract. OPC UA is widely considered an important communication standard for Industry 4.0, its use in manufacturing continues to grow, and it is likely to remain valuable for years to come.
In this article, we will turn a Raspberry Pi 5 into a SoftPLC with CODESYS and connect to it from UaExpert on a Windows PC using OPC UA. By the end, we will use the PLC to light an LED connected to GPIO18 and read a push-button input connected to GPIO23.
What We Will Build
The completed setup will look like this:
1. What Is OPC UA?
1.1 OPC UA Exchanges Industrial Data with Meaning and Context
OPC UA is a standard for exchanging data between devices and software in industrial automation. It is designed to work securely while reducing dependence on a particular vendor or operating system.
For example, imagine that a PLC contains the following variables:
rProcessValue = Current process value
rSetpoint = Target value
xOutput = Output state
xAlarmActive = Current alarm state
Instead of exposing only register numbers, OPC UA can present the data as a meaningful tree like this:
Objects
└─ MachineData
└─ GVL_Machine
├─ rProcessValue
├─ rSetpoint
├─ xOutput
└─ xAlarmActive
A client browses this tree to find the variable it needs.
Each variable is represented as a Node. A node contains not only a value, but also information such as:
NodeId
BrowseName / DisplayName
Data type
Read and write permissions
StatusCode
SourceTimestamp
ServerTimestamp
This ability to give values both structure and meaning is one of the most important ideas to understand about OPC UA.
1.2 Read, Write, and Subscription
At a minimum, it helps to distinguish between the following basic OPC UA operations.
Read
The client asks for the value at that moment.
Client: What is the current temperature?
Server: It is 42.3.
Write
The client sets a value on a variable for which writing is permitted.
Client: Change the setpoint to 60.0.
Server: The write request has been accepted.
Subscription / MonitoredItem
The client registers a variable to monitor, and the server sends a notification when its value changes.
Client: I want to monitor this variable.
Server: Its value has changed, so here is a notification.
2. What You Need
2.1 Hardware
Raspberry Pi 5
microSD card or NVMe SSD
A stable power supply suitable for the Raspberry Pi 5
A Windows 11 PC
Ethernet cable, recommended
One LED
One resistor of approximately 330 Ω
One tactile push button
One 10 kΩ resistor
Breadboard
Jumper wires
2.2 Software
Raspberry Pi OS Lite, 64-bit
CODESYS Development System
CODESYS Communication add-on
CODESYS Control for Raspberry Pi SL
CODESYS Control SL Deploy Tool
UaExpert
For a Raspberry Pi 5 running a 64-bit OS, use CODESYS Control for Raspberry Pi 64 SL as the project device.
Without a license, CODESYS Control for Raspberry Pi SL runs without functional limitations for two hours after startup and then stops as part of its demo behavior. Check the licensing terms before using it for long-running or commercial applications.
3. Prepare Raspberry Pi OS
Use Raspberry Pi Imager to install Raspberry Pi OS with settings similar to the following:
Device: Raspberry Pi 5
OS: Raspberry Pi OS Lite (64-bit)
Hostname: Any name you prefer, for example yfpi
Username: Any username you prefer
Password: A strong password
SSH: Enabled
Timezone: Asia/Tokyo
4. Turn the Raspberry Pi 5 into a CODESYS SoftPLC
4.1 Install CODESYS on Windows
Install CODESYS Development System.
Next, use CODESYS Installer to add at least the following packages:
CODESYS Control for Raspberry Pi SL
CODESYS Control SL Deploy Tool
CODESYS Communication
CODESYS Security Agent
If Communication Manager does not appear under Application → Add Object, check that the CODESYS Communication add-on is installed. According to the CODESYS workflow, the add-on makes it possible to add a Communication Manager and an OPC UA Server below the Application object.
4.2 Install the CODESYS Runtime on the Pi
In CODESYS, open:
Tools
└─ Deploy Control SL
Connect to the Raspberry Pi over SSH:
IP address: <Pi IP address>
Port: 22
Username: Raspberry Pi username
Password: Raspberry Pi password
Select CODESYS Control for Raspberry Pi SL as the product and install the runtime on the Pi.
At the time this article was written, the latest version used in this environment was 4.21.0.0. As much as possible, keep the Windows package, the runtime installed on the Pi, and the device description used by the project within the same version generation.
4.3 Create a New PLC Project
In CODESYS, select:
File
└─ New Project
└─ Standard Project
Use the following settings:
Device:
CODESYS Control for Raspberry Pi 64 SL
PLC_PRG language:
Structured Text (ST)
5. Start with a Simulated Machine—No GPIO Yet
If you try to configure GPIO, certificates, and OPC UA all at once, troubleshooting quickly becomes difficult.
We will begin by creating a simulated machine in the PLC program with the following behavior:
A Start command begins operation.
A Stop command stops operation.
The output turns on when the process value is below the setpoint.
The output turns off when the process value is above the setpoint.
An alarm is raised when the high limit is exceeded.
The alarm is latched and must be reset explicitly.
Setpoints and commands can be written through OPC UA.
Later,
xOutputwill be mapped to GPIO18.
5.1 Create a GVL
Right-click Application and select:
Add Object
└─ Global Variable List
Name it GVL_Machine.
VAR_GLOBAL
// Commands operated from an OPC UA client
xStartCmd : BOOL := FALSE;
xStopCmd : BOOL := FALSE;
xResetAlarmCmd : BOOL := FALSE;
xAutoMode : BOOL := TRUE;
xManualOutputCmd : BOOL := FALSE;
rSetpoint : REAL := 50.0;
rHighLimit : REAL := 80.0;
// States published by the PLC
xRunning : BOOL := FALSE;
xOutput : BOOL := FALSE;
xAlarmActive : BOOL := FALSE;
xAlarmLatched : BOOL := FALSE;
xHeartbeat : BOOL := FALSE;
rProcessValue : REAL := 20.0;
udiCycleCounter : UDINT := 0;
// This will later be mapped to GPIO23.
// The input is assumed to be active-low.
xPhysicalButtonRaw : BOOL := TRUE;
END_VAR
5.2 Write PLC_PRG
Enter the following code in PLC_PRG:
PROGRAM PLC_PRG
VAR
xRunLatch : BOOL := FALSE;
fbSimTick : TON;
fbHeartbeat : TON;
fbButtonFilter : TON;
rtButton : R_TRIG;
END_VAR
// -----------------------------
// PLC scan counter
// -----------------------------
GVL_Machine.udiCycleCounter :=
GVL_Machine.udiCycleCounter + 1;
// -----------------------------
// Physical button processing
// The GPIO input is assumed to be active-low.
// -----------------------------
fbButtonFilter(
IN := NOT GVL_Machine.xPhysicalButtonRaw,
PT := T#50ms
);
rtButton(CLK := fbButtonFilter.Q);
// -----------------------------
// Start and stop logic
// OPC UA commands are treated as one-shot commands.
// -----------------------------
IF GVL_Machine.xStartCmd OR rtButton.Q THEN
xRunLatch := TRUE;
GVL_Machine.xStartCmd := FALSE;
END_IF;
IF GVL_Machine.xStopCmd THEN
xRunLatch := FALSE;
GVL_Machine.xStopCmd := FALSE;
END_IF;
// -----------------------------
// Limit the configuration values to valid ranges.
// -----------------------------
IF GVL_Machine.rHighLimit < 20.0 THEN
GVL_Machine.rHighLimit := 20.0;
ELSIF GVL_Machine.rHighLimit > 100.0 THEN
GVL_Machine.rHighLimit := 100.0;
END_IF;
IF GVL_Machine.rSetpoint < 5.0 THEN
GVL_Machine.rSetpoint := 5.0;
ELSIF GVL_Machine.rSetpoint >
(GVL_Machine.rHighLimit - 5.0) THEN
GVL_Machine.rSetpoint :=
GVL_Machine.rHighLimit - 5.0;
END_IF;
// -----------------------------
// High-temperature alarm
// -----------------------------
GVL_Machine.xAlarmActive :=
GVL_Machine.rProcessValue >=
GVL_Machine.rHighLimit;
IF GVL_Machine.xAlarmActive THEN
GVL_Machine.xAlarmLatched := TRUE;
END_IF;
// Require a 5-degree margin before allowing an alarm reset.
IF GVL_Machine.xResetAlarmCmd THEN
IF GVL_Machine.rProcessValue <=
(GVL_Machine.rHighLimit - 5.0) THEN
GVL_Machine.xAlarmLatched := FALSE;
END_IF;
GVL_Machine.xResetAlarmCmd := FALSE;
END_IF;
// -----------------------------
// Automatic and manual output control
// -----------------------------
IF xRunLatch AND NOT GVL_Machine.xAlarmLatched THEN
IF GVL_Machine.xAutoMode THEN
// Hysteresis control with a ±1.0 band
IF GVL_Machine.rProcessValue <=
(GVL_Machine.rSetpoint - 1.0) THEN
GVL_Machine.xOutput := TRUE;
ELSIF GVL_Machine.rProcessValue >=
(GVL_Machine.rSetpoint + 1.0) THEN
GVL_Machine.xOutput := FALSE;
END_IF;
ELSE
GVL_Machine.xOutput :=
GVL_Machine.xManualOutputCmd;
END_IF;
ELSE
GVL_Machine.xOutput := FALSE;
END_IF;
// -----------------------------
// Simulated process with a 100 ms update interval
// -----------------------------
fbSimTick(
IN := NOT fbSimTick.Q,
PT := T#100ms
);
IF fbSimTick.Q THEN
IF GVL_Machine.xOutput THEN
// Output ON: the process value rises.
GVL_Machine.rProcessValue :=
GVL_Machine.rProcessValue + 0.40;
ELSE
// Output OFF: the process value falls.
GVL_Machine.rProcessValue :=
GVL_Machine.rProcessValue - 0.10;
END_IF;
IF GVL_Machine.rProcessValue > 100.0 THEN
GVL_Machine.rProcessValue := 100.0;
ELSIF GVL_Machine.rProcessValue < 0.0 THEN
GVL_Machine.rProcessValue := 0.0;
END_IF;
END_IF;
// -----------------------------
// One-second heartbeat
// -----------------------------
fbHeartbeat(
IN := NOT fbHeartbeat.Q,
PT := T#1s
);
IF fbHeartbeat.Q THEN
GVL_Machine.xHeartbeat :=
NOT GVL_Machine.xHeartbeat;
END_IF;
GVL_Machine.xRunning := xRunLatch;
5.3 Set MainTask to a 20 ms Cycle
Open Task Configuration → MainTask.
Type: Cyclic
Interval: T#20ms
Keep in mind that the following three cycles are separate:
PLC scan cycle: 20 ms
Simulated process update: 100 ms
OPC UA notification/view: Approximately 250–1000 ms
Reducing the OPC UA update interval does not mean that the control logic itself runs through OPC UA. The PLC executes its control program cyclically inside the runtime, while OPC UA exposes selected variables to external systems.
5.4 Download the Project to the Pi and Run It
Double-click the top-level Device and select the Pi under Communication Settings.
Scan Network
or enter the IP address directly
→ Set Active Path
Then run the following commands:
Build
└─ Build
Online
└─ Login
Debug
└─ Start
Open GVL_Machine online and confirm the following:
udiCycleCounter → Continues increasing
xHeartbeat → Alternates between TRUE and FALSE about once per second
6. Publish PLC Variables through OPC UA
6.1 Add a Communication Manager
Right-click Application and select:
Add Object
└─ Communication Manager
Right-click the new Communication Manager and select:
Add Object
└─ OPC UA Server
Next, right-click OPC UA Server and select:
Add Object
└─ IEC Symbol Publishing
Name the IEC Symbol Publishing object MachineData.
The final object tree should look like this:
Application
├─ GVL_Machine
├─ PLC_PRG
├─ Task Configuration
└─ Communication Manager
└─ OPC UA Server
└─ MachineData
(IEC Symbol Publishing)
In CODESYS, adding IEC Symbol Publishing below the OPC UA Server in the Communication Manager lets you publish project variables and IEC data types.
6.2 Add Individual Variables in the IEC Symbols Editor
Double-click MachineData and open the IEC Symbols Editor tab.
From Precompile Sets on the left, drag the individual variables you want to publish into the area on the right.
Start with the following variables:
GVL_Machine.xStartCmd
GVL_Machine.xStopCmd
GVL_Machine.xResetAlarmCmd
GVL_Machine.xAutoMode
GVL_Machine.xManualOutputCmd
GVL_Machine.rSetpoint
GVL_Machine.rHighLimit
GVL_Machine.xRunning
GVL_Machine.xOutput
GVL_Machine.xAlarmActive
GVL_Machine.xAlarmLatched
GVL_Machine.xHeartbeat
GVL_Machine.rProcessValue
GVL_Machine.udiCycleCounter
Variables That Clients May Write
xStartCmd
xStopCmd
xResetAlarmCmd
xAutoMode
xManualOutputCmd
rSetpoint
rHighLimit
Set their access rights to:
Read/Write
Read-Only Variables
xRunning
xOutput
xAlarmActive
xAlarmLatched
xHeartbeat
rProcessValue
udiCycleCounter
Set their access rights to:
Read
During the first connection test, if the following option is available, temporarily turning it off can make troubleshooting easier. It helps separate user-management permission problems from symbol-publishing configuration problems.
Use access rights defined in the user management = OFF
After the basic connection works, configure user management and grant only the permissions that are actually needed.
Important troubleshooting tip
In this setup, UaExpert did not show the expected variables when only the GVL name was registered. That is why this article adds each variable individually. Make sure the right-hand side contains one row per variable, rather than a single row namedGVL_Machine.
The CODESYS IEC Symbols Editor follows the same basic workflow: drag individual variables from Precompile Sets on the left into the publishing list on the right.
6.3 Refresh and Sync
After adding or changing variables, click Refresh at the top of the IEC Symbols Editor.
If error, warning, or information icons appear, check their tooltips. If Sync is available, run it as well.
The editor refers to the project's precompiled state, so changing variables later can cause the publishing configuration to become inconsistent.[^codesys-symbol-publishing]
6.4 Perform a Full Rebuild and Download
After changing the symbol-publishing configuration, a full download is more reliable than relying only on an online change.
Debug → Stop
Online → Logout
Build → Clean All
Build → Rebuild
Online → Login
Download
Debug → Start
If MachineData does not appear in UaExpert, a full rebuild and download may resolve the problem.
7. Create an OPC UA Server Certificate
7.1 Do Not Confuse It with the Encrypted Communication Certificate
In the CODESYS Security Screen, you may already see a certificate named:
Encrypted Communication
This certificate is mainly used for communication between CODESYS Development System and the runtime on the Pi.
For encrypted OPC UA communication between UaExpert and the Raspberry Pi, you need a separate certificate:
OPC UA Server certificate
Keeping these two certificates separate in your mind is important.
7.2 Check the Device Security Settings
Double-click the top-level Device and open Communication Settings.
From the menu in that view, open:
Device
└─ Security Settings
The main settings are:
Activation = ACTIVATED
CommunicationMode = SECURE_IF_POSSIBLE
UserAuthentication = ENABLED
With SECURE_IF_POSSIBLE, the OPC UA server can temporarily allow an unencrypted connection while no OPC UA Server certificate exists. After a certificate is created, the insecure mode is automatically disabled. Depending on your requirements, you can also use SIGNED_AND_ENCRYPTED or ALL.
To use Anonymous authentication during the initial test, open the following from Communication Settings in the same Device editor:
Device
└─ Change Runtime Security Policy
└─ Allow anonymous login = ON
Use this only while testing. After confirming that username-based authentication works, turn it back off. CODESYS requires Anonymous access to be allowed explicitly.
If you connect by IP address and want the IP address included in the certificate, configure one of the following before creating the certificate:
CertificateIPAdresses = All
Or specify an address:
CertificateIPAdresses = 192.168.1.14
Depending on the CODESYS version, the setting name may appear with the spelling Adresses.[^codesys-security-settings]
7.3 Create the Certificate in the Security Screen
With the Pi selected as the Active Path, open:
View
└─ Security Screen
On the Devices tab, select the Device itself on the left.
The right side lists services that require certificates, such as:
Encrypted Communication
OPC UA Server
...
Select OPC UA Server and click the certificate creation icon.
In the CODESYS workflow, the certificate created here is created on the controller. Restart the runtime after creating it.[^codesys-certificate]
sudo reboot
Where Is the Certificate Stored?
The server certificate and its private key are held by the CODESYS runtime on the Raspberry Pi.
Raspberry Pi
└─ CODESYS Runtime certificate store
├─ OPC UA Server certificate
├─ Corresponding private key
├─ Trusted Certificates
└─ Quarantined Certificates
CODESYS Development System acts as a remote management interface for this certificate store.
8. Connect Securely from UaExpert
8.1 What Is UaExpert?
UaExpert is an OPC UA test client provided by Unified Automation.
You can use it to check features such as:
OPC UA Server discovery
Address Space browsing
Variable Read / Write operations
Subscriptions and MonitoredItems
Trend displays
Server diagnostics
Certificate information
For this article, install UaExpert on the Windows PC.
8.2 Add the Server
From the menu at the top of UaExpert, select:
Server
└─ Add...
You can also use the + icon on the toolbar.
Enter the Pi's IP address as the Discovery URL:
opc.tcp://<Pi IP address>:4840
For example:
opc.tcp://192.168.1.14:4840
8.3 Choose a Security Policy
The following endpoints appeared in this environment:
Aes256_Sha256_RsaPss - Sign
Aes256_Sha256_RsaPss - Sign & Encrypt
Select:
Aes256_Sha256_RsaPss - Sign & Encrypt
As a general rule, choose a Sign & Encrypt endpoint from the secure endpoints offered by the server.
For the initial troubleshooting step, use:
UserTokenType:
Anonymous
8.4 Trust the Server Certificate in UaExpert
On the first connection, UaExpert will treat the Raspberry Pi's OPC UA Server certificate as untrusted.
Review the certificate details. After confirming that it belongs to your Pi, select:
Trust Server Certificate
For a temporary test, you can instead choose:
Accept the server certificate temporarily for this session
8.5 Trust the UaExpert Certificate in CODESYS
In CODESYS, open:
View
└─ Security Screen
└─ Devices
Select Quarantined Certificates and look on the right for a certificate similar to:
UaExpert@<Windows PC name>
Check the subject, Application URI, and validity period to confirm that it is your UaExpert certificate.
In versions that do not offer Trust on the right-click menu, drag the certificate row from the right side into Trusted Certificates on the left:
Quarantined Certificates
↓ Drag
Trusted Certificates
When using self-signed certificates, the client must trust the server certificate, and the server must trust the client certificate. This establishes mutual application trust for the secure connection.
Return to UaExpert and connect again.
If the connection succeeds, Root → Objects will appear in the Address Space view.
9. Monitor PLC Variables from UaExpert
9.1 Browse the Address Space
After connecting, expand the Address Space in UaExpert.
The exact hierarchy may vary slightly by environment, but in this setup it looked roughly like this:
Root
└─ Objects
└─ MachineData
└─ GVL_Machine
├─ xStartCmd
├─ xRunning
├─ xOutput
├─ rSetpoint
└─ rProcessValue
9.2 Drag Variables into Data Access View
Data Access View is empty immediately after you connect.
Drag the following variables from the Address Space into Data Access View:
udiCycleCounter
xHeartbeat
xRunning
xOutput
rProcessValue
rSetpoint
rHighLimit
xAlarmActive
xAlarmLatched
UaExpert creates a Subscription and a MonitoredItem for each variable you drag in, then displays its current value and subsequent changes.[^uaexpert-data]
Check the following:
StatusCode = Good
udiCycleCounter continues increasing
xHeartbeat alternates between TRUE and FALSE
9.3 Write Values from UaExpert
For a writable variable, double-click its Value cell in Data Access View and enter a new value.[^uaexpert-data]
First, set up the following initial state:
xAutoMode = TRUE
rSetpoint = 50.0
rHighLimit = 80.0
If xAlarmLatched = TRUE, clear the alarm first. The reset command will only clear the latch after rProcessValue has fallen to at least 5 degrees below rHighLimit.
Start the simulated machine:
xStartCmd = TRUE
Expected result:
xStartCmd → Quickly returns to FALSE
xRunning → TRUE
xOutput → TRUE
rProcessValue → Begins rising
Because the PLC resets xStartCmd to FALSE immediately after accepting it, UaExpert may not display the TRUE state long enough for you to notice it.
Check whether the command succeeded by looking at the resulting state variable rather than the command variable itself:
Command: xStartCmd
Result: xRunning
9.4 Change the Setpoint
Write the following value:
rSetpoint = 60.0
Expected result:
rProcessValue moves toward approximately 60
xOutput turns ON at 59 or below
xOutput turns OFF at 61 or above
This is hysteresis control with a ±1.0 band.
9.5 Try the Stop Command
xStopCmd = TRUE
Expected result:
xRunning = FALSE
xOutput = FALSE
9.6 Try Manual Output Control
xStartCmd = TRUE
xAutoMode = FALSE
xManualOutputCmd = TRUE
Expected result:
xOutput = TRUE
To turn it off:
xManualOutputCmd = FALSE
However, setting the manual command to TRUE will not activate the output in either of the following states:
xRunning = FALSE
or
xAlarmLatched = TRUE
10. Watch the Control Behavior in Trend View
Add a Trend View document in UaExpert.
The exact menu name can vary slightly with the window layout and UaExpert version, but you can usually open it from the menu for adding a new document.
Add rProcessValue, rSetpoint, and xOutput to the trend. This makes it easy to see the process value approach the setpoint and the output switch on and off across the hysteresis band.
11. Connect an LED and Push Button to the Raspberry Pi 5 GPIO
Once the simulated machine and OPC UA connection are working correctly, you can move on to physical GPIO.
11.1 There Are Three Different GPIO Numbering Systems
This was one of the most confusing parts of the setup.
GPIO18 on the Raspberry Pi 5 can be described in at least three different ways:
| Numbering system | LED connection |
|---|---|
| BCM GPIO number | 18 |
| Physical pin on the 40-pin header | 12 |
| Global Sysfs GPIO number in this environment | 589 |
GPIO23 is represented as follows:
| Numbering system | Button connection |
|---|---|
| BCM GPIO number | 23 |
| Physical pin on the 40-pin header | 16 |
| Global Sysfs GPIO number in this environment | 594 |
11.2 Wiring
11.3 Add a GPIO Device in CODESYS
Add a GPIO connector to the Device, then add GPIOs 4 bit below it:
Device
└─ GPIO
└─ GPIOs 4 bit
It is not a problem if the Vendor is displayed as CODESYS.
GPIOs 4 bit provides four logical channels:
Bit1
Bit2
Bit3
Bit4
The mapping view shows both input and output entries:
Bit1 in
Bit2 in
Bit3 in
Bit4 in
Bit1 out
Bit2 out
Bit3 out
Bit4 out
This does not mean that there are eight GPIO lines. It shows input and output mapping options for each of the four logical channels.
12. Find the Sysfs GPIO Numbers on a Raspberry Pi 5
The GPIO module used in this setup relied on Sysfs.
The Linux GPIO Sysfs API is an older interface, and the Linux kernel documentation recommends migrating to the GPIO Character Device API. However, the CODESYS IoDrvGpioSysfs driver used in this article required global Sysfs GPIO numbers in its Parameters.
12.1 Find the GPIO Chip Base Number
Connect to the Pi over SSH and run:
for chip in /sys/class/gpio/gpiochip*; do
echo "=== $chip ==="
echo -n "label: "
cat "$chip/label"
echo -n "base: "
cat "$chip/base"
echo -n "ngpio: "
cat "$chip/ngpio"
done
On the Raspberry Pi 5 used for this article, the output included:
=== /sys/class/gpio/gpiochip571 ===
label: pinctrl-rp1
base: 571
ngpio: 54
On Raspberry Pi 5, the RP1 chip handles external I/O functions including GPIO.
12.2 Convert BCM Numbers to Sysfs Numbers
Use the following formula:
Global Sysfs GPIO number
= pinctrl-rp1 base + BCM GPIO number
In the older global GPIO number space used by Sysfs, a line number is obtained by combining the GPIO chip's base with the line offset.
For this environment:
GPIO18 = 571 + 18 = 589
GPIO23 = 571 + 23 = 594
Do not copy
571blindly into another environment.
The GPIO chip base can vary. Always check it on the actual device you are using.
12.3 Configure the Parameters
Open the Parameters for GPIOs 4 bit.
Use the following settings for this environment:
Bit1:
GPIO Number = 589
Direction = OUTPUT
Bit2:
GPIO Number = 594
Direction = INPUT
Bit3:
Unused
Bit4:
Unused
When I initially entered 18 and 23 directly, the GPIO module did not work correctly. With the Sysfs-based driver on this Raspberry Pi 5 environment, I had to use 589 and 594 instead.
12.4 Configure the I/O Mapping
Use only the mapping entry that matches the direction configured in Parameters.
For the first output test, map:
Bit1 out
└─ GVL_Machine.xHeartbeat
Starting with xHeartbeat instead of xOutput is recommended.
Because xHeartbeat changes state about once per second, it makes it much easier to test the GPIO path independently from the rest of the control logic.
For the input, map:
Bit2 in
└─ GVL_Machine.xPhysicalButtonRaw
Leave the unused rows empty:
Bit1 in → Empty
Bit2 out → Empty
Bit3 in/out → Empty
Bit4 in/out → Empty
12.5 Perform a Full Rebuild and Download
After changing the GPIO device configuration, run:
Debug → Stop
Online → Logout
Build → Clean All
Build → Rebuild
Online → Login
Download
Debug → Start
In this setup, the configuration initially failed to work even though the values were correct. Rebuilding the project and running it again resolved the issue.
After changing the device configuration or I/O mapping, a full rebuild and download is more reliable than an online change alone.
12.6 Confirm That the LED Blinks
After the PLC enters RUN, confirm that:
xHeartbeat alternates between TRUE and FALSE
The LED on GPIO18 blinks about once per second
If this works, the following path is operating correctly:
PLC_PRG
↓
GVL_Machine.xHeartbeat
↓
Bit1 out
↓
Sysfs GPIO589
↓
BCM GPIO18
↓
Physical pin 12
↓
LED
After confirming the GPIO output by itself, change the mapping to:
Bit1 out
└─ GVL_Machine.xOutput
The physical LED will now reflect the result of Start commands and setpoint changes made from UaExpert.
12.7 Run the Complete Test
13. What This Experiment Teaches Us About OPC UA
13.1 The Address Space Is a Map of Meaningful Machine Data
Being able to browse a structure such as MachineData → GVL_Machine → rProcessValue makes it clear that OPC UA is more than a way to exchange raw bytes.
13.2 A Node Contains More Than a Value
UaExpert displays the value together with information such as the data type, StatusCode, timestamps, and NodeId.
13.3 Subscriptions Make Monitoring More Efficient
In Data Access View and Trend View, we did not repeatedly read each value by hand. Instead, MonitoredItems delivered notifications as values changed.
13.4 SecureChannel and User Authentication Are Separate
In this experiment, certificates established a Sign & Encrypt connection, while the initial test used an Anonymous UserToken.
Certificates
→ Application trust and communication protection
UserToken
→ Authentication of the user accessing the server
13.5 PLC Control and OPC UA Communication Are Separate
The control logic runs in the PLC task on a 20 ms cycle.
OPC UA securely exposes selected control variables to higher-level systems and accepts setpoints and commands from them.
13.6 The Data Path Reaches Real Physical I/O
The final output path in this experiment is:
UaExpert
↓ Write
OPC UA Server
↓
GVL_Machine.xStartCmd
↓
PLC logic
↓
GVL_Machine.xOutput
↓
CODESYS GPIO Sysfs Driver
↓
Sysfs GPIO589
↓
BCM GPIO18 / physical pin 12
↓
LED
The input path runs in the opposite direction:
Push button
↓
BCM GPIO23 / physical pin 16
↓
Sysfs GPIO594
↓
Bit2 in
↓
GVL_Machine.xPhysicalButtonRaw
↓
PLC logic
↓
OPC UA Subscription
↓
UaExpert
Once you reach this point, OPC UA no longer feels like a feature that simply lets a PC view variables inside a PLC. You can see it as a complete way to connect a controller with higher-level systems—including the information model, security, commands, monitoring, and physical I/O.
Conclusion
In this article, we turned a Raspberry Pi 5 into a SoftPLC with CODESYS and connected to it from UaExpert using OPC UA.
Reading the OPC UA specification is useful, but the pieces become much easier to understand when you try the complete workflow yourself: publish variables, establish certificate trust, write a command, and watch a real LED turn on as a result.
That end-to-end experience makes the role of each OPC UA component feel concrete—and gives you a practical foundation for connecting real control equipment to supervisory systems, dashboards, data platforms, and other industrial applications.





