Objective:
-
Set up a persistence solution on a RevPi using the CODESYS Application Composer.
-
Apply it in a sample application to restore configuration data and a hardware counter automatically after a restart.
RevPi devices do not have dedicated hardware EEPROM for CODESYS retain variables or persistent variables.
The RETAIN and PERSISTENT RETAIN keywords are not supported by default on these devices.
The solution is the CODESYS Application Composer PersistenceManager, which stores variable data as ASCII files on the onboard eMMC flash memory.
This tutorial applies to all RevPi base modules.
A preprogrammed example project is available in the GitLab repository. This example was developed and tested on the RevPi Connect 5. To adapt it to another RevPi, the device in the CODESYS device tree must be changed. The persistence configuration itself remains identical.
The project uses three persistence channels, each with its own storage strategy:
-
Channel_Config: Saves setpoints and parameters immediately when a value changes and on PLC stop. -
Channel_Counters: Saves runtime counters and totals periodically every minute and on PLC stop. -
Channel_Critical: Saves safety states and alarms immediately when a value changes and on PLC stop.
|
Caution
|
Premature eMMC wear due to improper channel assignment
▷ Use the ▷ Use the |
Prerequisites #
Devices
Hardware Installation
✓ Expansion module RevPi DIO is connected to the right side of the RevPi Connect 5 via the PiBridge plug connector.
✓ RevPi base module is connected to the network.
✓ RevPi base module is connected to the power supply.
✓ RevPi DIO is connected to the power supply.
Software Installation
✓ CODESYS Development System (CODESYS) V3.5 SP21 or higher is installed on your PC.
✓ CODESYS Runtime is installed in the CODESYS Development System.
✓ RevPi I/O driver is installed in the CODESYS Development System.
✓ CODESYS is connected to the RevPi.
✓ CODESYS Runtime is installed on the RevPi.
✓ A new standard project with a RevPi Connect 5 has been created (with CODESYS Control for Linux ARM64 SL in Structured Text).
See also: Set up system.
1. Installing the Application Composer Add-On #
The Application Composer is not included in the base installation of the CODESYS IDE. It must be installed separately as a free add-on via the CODESYS Installer. The PersistenceManager module used in this tutorial does not require a paid license.
▷ Open CODESYS.
▷ Select .
❯ The CODESYS Installer opens.
▷ Close CODESYS.
▷ Use the CODESYS Installer to install the latest version of the CODESYS Application Composer add-on.
▷ Start CODESYS.
❯❯ The Composer item is now available in the menu bar.
|
Note
|
If the Composer menu is not visible after installation, press Alt+2 with the CODESYS IDE open. Alternatively, create a CODESYS desktop shortcut and add the parameter |
2. Adding the Persistence Library #
The AC_Persistence library is an Application Composer library for managing persistent data. It provides both the PLC library for accessing the persistence functions and the associated Composer modules.
AC_Persistence must be added both via the Library Manager and via the Composer menu. Both steps are required so that it is available for the code and the modules are registered in the Composer.
Adding via the Library Manager
▷ Open the Library Manager in the device tree.
▷ Select Add Library.
▷ Search for AC_Persistence and select AC_Persistence (CODESYS). Select the latest available version.
▷ Select OK.
❯ AC_Persistence is added to the list of libraries.
Adding via the Composer
▷ Select .
▷ In the dialog, enable AC_Persistence (CODESYS).
|
Note
|
Two editions are available: one from 3S - Smart Software Solutions GmbH and one from CODESYS. Make sure to select the CODESYS edition. |
▷ Select OK.
3. Configuring the Persistence Manager #
Adding the Persistence Manager
The Persistence Manager is a project object in CODESYS for managing persistent variables.
▷ Select or press Alt+2.
❯ The Modules tab is displayed.
▷ Open the context menu of the project and select Add Top-Level Module Instance.
▷ Enter PersistenceManager in the Name field.
▷ In the module list, select PersistenceManager with Type AC_PERSIST.PersistenceManager.
▷ Set Application to Device.Application.
▷ Select Add Module.
❯ The PersistenceManager is added to the module tree.
Creating Persistence Channels
▷ Open the context menu of Channels in the module tree.
▷ Select Add Submodule Instance.
▷ For Module, select PersistenceChannel.
▷ Enter the instance name Channel_Config and select Add Module.
Repeat these steps for Channel_Counters and Channel_Critical.
| Instance Name | Type | Purpose |
|---|---|---|
|
PersistenceChannel |
Setpoints and parameters |
|
PersistenceChannel |
Runtime counters and totals |
|
PersistenceChannel |
Safety states and alarms |
CODESYS automatically creates a persistence group with the same name as the channel instance. No manual group configuration is required. The group names Channel_Config, Channel_Counters, and Channel_Critical are immediately available for use in the ac_persist attribute.
Adding the Storage Type
▷ Add the ASCIIFileStorage storage type to each persistence channel in the module tree via the Data Storage node.
After completing this step, the module tree should have the following structure:
Configuring Channel Parameters
▷ Open the channels in the module tree to open the corresponding channel editor.
▷ Select the Parameters tab and set the following values:
| Parameter | Channel_Config | Channel_Counters | Channel_Critical |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note
|
For this tutorial, the xIntegrityCheckBeforeReading parameter is set to FALSE so that no restore errors occur during the first start without an existing persistence file. |
Why the Channel Choice Matters:
The persistence channels write their data to the onboard eMMC according to their storage strategy. The choice of channel therefore directly affects the write load and the service life of the flash memory.
-
Channel_ConfigandChannel_CriticalusexSaveOnChange = TRUE.
Each time an assigned variable changes, the persistence file is rewritten. These channels are suitable only for values that change rarely, such as setpoints, parameters, alarm states, or emergency stop flags.
The name Channel_Critical describes the importance of the data, not how often it changes. It should therefore be used only for important states that change rarely.
-
Channel_Countersuses periodic saving viatPeriodicSaving.
Changes are collected and written only at the next save interval. This makes the channel suitable for values that change frequently, such as counters, totals, or operating hours.
Rule: Assign persistence channels by change frequency, not by importance.
If frequently changing data must survive a restart or power failure, use a periodic channel and reduce the save interval if necessary instead of switching to xSaveOnChange.
|
Caution
|
Premature eMMC wear due to improper channel assignment
▷ Use the ▷ Use the |
4. Configuring the Hardware Counter #
Input CH1 on a RevPi DIO is configured as a counter for rising edges.
The counter value is saved in Channel_Counters so that it survives a runtime restart.
▷ Add a RevPi DIO to the piBridgeRight object in the CODESYS device tree.
▷ Open the module editor of RevPi_DIO in the device tree.
▷ Select Revolution PI DIO Module Parameters.
▷ Set Input Mode Ch 1 to Counter, rising edge. All other channels remain set to Direct.
▷ Select Revolution PI DIO Module I/O Mapping.
▷ In the Digital Inputs (Channel 1-14) section, go to DI_CH1 at address %IX8.0 (type BOOL) and enter xInputCH1 in the Variable column.
▷ In the same section, go to Counter_Ch1 at address %ID3 (type DINT) and enter diCounterCH1 in the Variable column.
❯ The counter variables are assigned.
5. Creating Persistent Variables #
Creating a Global Variable List
▷ Open the context menu of Application in the device tree.
▷ Select .
▷ Enter the name GVL_Persistent.
▷ Select Add.
❯ GVL_Persistent opens.
Assigning Variables to the Channels
▷ Declare the persistent variables with the ac_persist attribute to assign each variable to its channel:
{attribute 'qualified_only'}
VAR_GLOBAL
// CONFIG channel - saves on change (setpoints, parameters)
{attribute 'ac_persist' := 'Channel_Config'}
rSetpointTemp : REAL := 20.0;
{attribute 'ac_persist' := 'Channel_Config'}
rSetpointPressure : REAL := 1.0;
{attribute 'ac_persist' := 'Channel_Config'}
eOperationMode : INT := 0;
// COUNTERS channel - saves periodically (default T#1m)
{attribute 'ac_persist' := 'Channel_Counters'}
uiRunCycles : UINT := 0;
{attribute 'ac_persist' := 'Channel_Counters'}
udiTotalRunHours : UDINT := 0;
{attribute 'ac_persist' := 'Channel_Counters'}
udiTotalProductCount : UDINT := 0;
{attribute 'ac_persist' := 'Channel_Counters'}
// Counter for rising edge of DIO input CH1, assigned via I/O Mapping
diCounterCH1 : DINT := 0;
// CRITICAL channel - saves on change, PLC stop, and online change
{attribute 'ac_persist' := 'Channel_Critical'}
xEmergencyStopActive : BOOL := FALSE;
{attribute 'ac_persist' := 'Channel_Critical'}
sLastAlarmMessage : STRING := '';
{attribute 'ac_persist' := 'Channel_Critical'}
dtLastShutdownTime : DT := DT#2024-1-1-0:0:0;
END_VAR
|
Note
|
The value of the |
How the Hardware Counter Works and its Storage Strategy:
How the Variables Work
The I/O mapping variables are automatically generated by CODESYS in IoConfig_Globals_Mapping. They cannot be used directly as persistence targets. xInputCH1 is read into a local variable in PLC_PRG in each scan cycle, and the delta of diCounterCH1 is accumulated in GVL_Persistent.diCounterCH1.
Storage Strategy for Numeric Counter Variables
In AC_Persistence, xSaveOnChange is effective only for BOOL variables. For numeric types such as DINT, change detection is not supported. The channel therefore does not trigger a save operation when the value is incremented. Periodic saving via tPeriodicSaving is therefore the only automatic save trigger for diCounterCH1. This makes Channel_Counters the correct channel for this variable.
This means that, during an unclean runtime restart, counter values up to the length of one save interval can be lost. The default value tPeriodicSaving = T#1m results in a maximum loss window of 60 seconds. If an application requires a shorter window, tPeriodicSaving for Channel_Counters should be reduced, e. g. to T#10s.
|
Note
|
Shorter intervals increase the write frequency to the onboard eMMC and thus its wearing. ▷ Therefore, choose a value that fits the expected service life of your application. |
Channel Naming
The channel in this project is named Channel_Counters because it contains software counters and totals:
-
uiRunCycles, -
udiTotalRunHoursand -
udiTotalProductCount.
The hardware input counter diCounterCH1 fits the same periodic storage strategy and is therefore added to the same channel. The name Channel_Counters refers to the group with the same storage strategy, not exclusively to hardware counters.
If the distinction is important in your project, rename the channel, e. g. to Channel_Periodic, and update all ac_persist attributes accordingly.
6. Creating PLC_PRG #
The main program increments a runtime counter every second and adds the hardware counter of CH1 of the RevPi DIO to the persistent total value in each scan cycle.
The Persistence Manager does not need to be called manually. The Application Composer handles all save and restore logic via the generated AC_ wrapper programs.
PROGRAM PLC_PRG
VAR
tonCycleTimer : TON;
xDIO_CH1_State : BOOL; // State of digital input CH1 (counter mode, rising edge)
diCounterCH1_Prev : DINT; // Hardware counter value from the previous IEC cycle
END_VAR
// Read I/O mapping variables in every IEC cycle
xDIO_CH1_State := xInputCH1;
// diCounterCH1 always starts at 0 after device start or codesyscontrol restart.
// Add delta from hardware counter in each scan to the persistent total value.
GVL_Persistent.diCounterCH1 := GVL_Persistent.diCounterCH1 + (diCounterCH1 - diCounterCH1_Prev);
diCounterCH1_Prev := diCounterCH1;
tonCycleTimer(IN := NOT tonCycleTimer.Q, PT := T#1S);
IF tonCycleTimer.Q THEN
GVL_Persistent.uiRunCycles := GVL_Persistent.uiRunCycles + 1;
END_IF
diCounterCH1 is a read-only input mapping that always starts at 0 after a device start or a codesyscontrol restart. In each scan, the delta between the current and previous hardware counter value is added to GVL_Persistent.diCounterCH1. This variable contains the restored persistent total value. Because both diCounterCH1 and diCounterCH1_Prev are initialized with 0, the delta in the first scan is 0 and the persistent value is preserved correctly:
|
Note
|
If the counter value must be processed before saving, e. g. to scale it or to transfer it to a separate total, declare a normal |
7. Generating Composer Code and Configuring Tasks #
Running Generate
|
Note
|
Run only after all persistence channels and their ASCIIFileStorage instances have been created. Otherwise, generation fails. |
▷ Select .
▷ Check the Messages window and make sure that no errors are reported.
❯ If generation is successful, the Application Composer automatically creates the following in the project tree:
-
Folder
AC_FBswith the generated function blocks. -
Folder
AC_Stdwith the following program objects:-
AC_CALL_HIGH_START -
AC_CALL_HIGH_END -
AC_CALL_LOW_START -
AC_CALL_LOW_END.
-
-
Folder
AC_RMPwith program objectAC_PRG_RMP. -
Three IEC tasks in the Task Configuration:
HIGH(Priority 1),LOW(Priority 10), andAC_RMP_Task(Priority 30). -
The
AC_programs are automatically assigned by the Composer to theHIGH,LOW, andAC_RMP_Tasktasks. -
A configuration node
PersistenceConfigunder Application. -
MainTaskwithPLC_PRG.
The AC_ wrapper calls must be configured manually in the next step.
Configuring MainTask
PLC_PRG runs in MainTask and must be enclosed by the AC_ persistence calls. This allows the PersistenceManager to safely capture variable values before and after the user application is executed in each cycle.
▷ Open the context menu of MainTask in the project tree.
▷ Select Add Call.
▷ Select AC_CALL_HIGH_START.
▷ Select OK.
▷ Repeat the last three actions with
-
AC_CALL_LOW_START,
-
AC_CALL_HIGH_END, and
-
AC_CALL_LOW_END.
▷ Use Move Up and Move Down to create the final order as follows:
MainTask (20 ms, Priority 1)
1. AC_CALL_HIGH_START
2. AC_CALL_LOW_START
3. PLC_PRG
4. AC_CALL_HIGH_END
5. AC_CALL_LOW_END
|
Note
|
PLC_PRG must remain between the start calls and the end calls. Check each entry carefully when adding it to avoid errors in program selection. ▷ Use Change Call to correct errors without having to remove and add the entry again. |
8. Build and Deployment #
▷ Select .
Make sure that the output shows no errors.
▷ Select .
▷ Select .
9. Testing the Persistence Function #
Monitoring Variables Online
▷ Open GVL_Persistent in the project tree.
▷ Open the Online View.
❯ All ten persistent variables are displayed with their live values and updated in each scan cycle.
▷ To test that Channel_Config saves on changes, enter a new value in the Prepared value column next to rSetpointTemp in the Online View.
▷ Then open the context menu anywhere in the variable list and select Write All Values of 'Device.Application' to apply the value.
❯ The new value is transferred from the Prepared value column to the Value column and should be written to the persistence file within a few seconds.
Checking Persistence Files
▷ After the application has been running for at least one minute, log in to the RevPi via terminal.
▷ Check whether the persistence files were created:
sudo ls -la /var/opt/codesys/PlcLogic/ac_persistence/
sudo cat /var/opt/codesys/PlcLogic/ac_persistence/Channel_Config
sudo cat /var/opt/codesys/PlcLogic/ac_persistence/Channel_Counters
sudo cat /var/opt/codesys/PlcLogic/ac_persistence/Channel_Critical
A correctly working setup produces output similar to the following for Channel_Config:
DT#2026-03-24-10:38:21 ___xCompressTags BOOL:TRUE GVL_Persistent.eOperationMode INT:0 <rSetpointPressure REAL: 1.0 <rSetpointTemp REAL: 20.0 ___Integrity BOOL:TRUE
|
Note
|
|
Testing Restore After Restart
▷ Note the current values of uiRunCycles, rSetpointTemp, and diCounterCH1 in the Online View.
▷ Restart the CODESYS Runtime on the RevPi via SSH:
sudo /etc/init.d/codesyscontrol restart
▷ Select to log back in from CODESYS.
▷ Open GVL_Persistent in the Online View and check whether:
-
uiRunCyclescontinues from the last saved value instead of being reset to 0. -
rSetpointTempretains the previously saved value instead of falling back to the declaration default of 20.0. -
GVL_Persistent.diCounterCH1continues from the last saved total value instead of being reset to 0.
❯ A successful result confirms that all three channels save and restore correctly.
Because Channel_Counters saves every minute, up to 60 seconds of counter values can be lost during an unclean restart. This is the expected trade-off when periodically saving a frequently changing counter.
Troubleshooting #
| Problem | Cause | Solution |
|---|---|---|
|
|
Use the |
|
The group name in the attribute does not match the group name of the channel. |
Open the Persistence tab of the channel and copy the exact group name. |
|
ASCIIFileStorage was not added to the channel. |
Add ASCIIFileStorage under the Data Storage node for each channel. |
|
Generate was run before channels were added. |
Add all three channels and ASCIIFileStorage before running Generate. |
Add Module button greyed out |
|
Open the Library Manager, add |
|
First run without an existing persistence file. |
Normal behavior. The file is created after the first save cycle. |
|
|
Set |
Variables are not saved |
|
Set |
PersistenceManager not visible in the module tree |
|
Run both steps for adding the library in the correct order. |