LLExec Dev

From Axel Public Wiki
Revision as of 14:33, 9 April 2024 by Axelpwiki (talk | contribs) (→‎Compile the plugin)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

Summary

The purpose of this page is to allow expert developers to extend the LLExec PLC runtime.

Prerequisites

To follow this guide you need:

  • A Windows PC with a running and licensed LLExec PLC runtime. You can download and install the AxelAutomationSuite from site https://www.axelsoftware.it/download
  • PluginHeaders SDK that must match the LLExec version; please ask this package to support@axelsw.it
  • Microsoft Visual Studio 2022

Setup your Windows machine

Create a directory and uncompress the PluginHeaders in it. Plugin headers expose the LLExec API to interface with the runtime.
Please note that every release of LLExec has its own plugin headers, so be sure that the release number of the headers matches with the release of LLExec installed. No backward compatibility is guaranted: compile your plugin for a specific release of LLExec.

Compile the plugin

Please open with Visual Studio the project

C:\plugin_headers_root\LLXPlugin_EXAMPLE\LLXPlugin_EXAMPLE.vcxproj

Please compile it. If you have done correctly all the previous steps, this will generate the file C:\plugin_headers_root\TestProgram\LLXPlugin_EXAMPLE.dll (with "Debug" configuration) or C:\plugin_headers_root\Build\LLXPlugin_EXAMPLE.dll (with "Release" configuration).

Copy this file on the your Windows machine in the same directory where the runtime is installed, for example

C:\Program Files (x86)\Axel PC Tools\LLExec

If you have compiled the plugin using "Debug" configuration, you can then debug it by attaching the VisualStudio debugger to the LLExec process (using "Debug / Attach to process" menu command).

Plugin concepts

LLExec is a modular runtime designed to run on top of (realtime) operating systems; we also have another type of runtime that can be embedded in the firmware of the board, but this is not the case.
Everything in the runtime is a plugin, the runtime itself is a plugin, while the core module (LLExec) is only responsible for load and link the modules together, implements communication with LogicLab and schedules all the tasks involved.
There are many plugins implementing various features like fieldbusses, local I/O and so on.
If you want to extend the runtime to manage your I/O, you have to implement and link your plugin.
The simplest way a plugin can expose its I/Os to the PLC is by means of data blocks, that are regions of memory, with a logical address, shared between the plugin and the PLC code.
You can also define functions that are invoked syncronously with the PLC tasks, and functions that will be exposed to the PLC and called from the PLC code, like standard IEC 61131 functions.
Once you have written your plugin, you can write a library, which can be included in your PLC program, to access the entities you have published in your plugin.
In the next chapters you will see the detail of the plugin EXAMPLE, and the details of the pll library that exposes the plugin features.
At last you have to modify LLExec configuration file LLExec.conf in order to run your plugin.

Plugin details

The plugin LLXPlugin_EXAMPLE is an example of the common features that can be implemented in a plugin.
The plugin is composed by several files:

  • Project files for many platforms.
  • LLXPlugin_EXAMPLE.cpp: this file contains all the interfaces with LLExec core, in here are defined data blocks and functions to be called.

The fastest way to implement a plugin is to copy and refactor one plugin already written, like this one.
Let's do a closer look to LLXPlugin_EXAMPLE.cpp:
the first section we want to look at is the following

static LLEXEC_DBDEF m_DataBlocks[] =
{
   // example datablock: it will be %MD5.0, 100 elements of 32bit each, read/write, no procImg, allocated by LLExec  
   { FALSE, DBTY_MEMO, 5,  0,                              100,                        sizeof(int32_t),    DBRW_RW, },
   // example datablock: c variable made visible to PLC, 10 elements of float, read-only
   { FALSE, DBTY_INP,  6,  (uintptr_t)&m_internalVar,      ARRDIM(m_internalVar),      sizeof(float32_t),  DBRW_R, },
   // example datablock: array of 16 digital inputs
   { FALSE, DBTY_INP,  10, (uintptr_t)&m_digitalInputs,    ARRDIM(m_digitalInputs),    sizeof(bool_t),     DBRW_R, },
   // example datablock: array of 16 digital outputs
   { FALSE, DBTY_OUT,  10, (uintptr_t)&m_digitalOutputs,   ARRDIM(m_digitalOutputs),   sizeof(bool_t),     DBRW_R, },
};

This defines the datablocks used to expose input and output to PLC.

  1. field says that both data blocks don't have an automatic process image
  2. field is the type of data block, that can be input, output or memory (DBTY_MEMO), this type is a part of the logical address
  3. field is the index of the data block, which in turn is part of the logical address
  4. field is the physical address of the data block, where zero means that the LLExec core must allocate automatically the memory at any address
  5. field is the number of elements
  6. field is the size of each element
  7. field if the read-write access

Once we have defined the memory regions that we must read and write to perform I/O, we must define a synchronized function wich can do the data exchange between data blocks and actual hardware:

static void beforeIOFunction()
{
  // do data exchange here
}


This function have to be called every I/O cycle, before PLC execution. This is defined in the next section

static LLEXEC_FUNDEF m_Functions[] =
{
   // example system Init function
   { LLFUNCT_INIT_L0,  (void *)initFunction,           "EXAMPLE_initFunction", },
   // example system Shutodwn function
   { LLFUNCT_SHUTDOWN, (void *)shutdownFunction,       "EXAMPLE_shutdownFunction", },
   // example PLC function
   { LLFUNCT_PLC,      (void *)exampleFunction,        "EXAMPLE_exampleFunction", },
   // example function to be called before every background PLC run
   { LLFUNCT_BACKGND,  (void *)backgroundFunction,     "EXAMPLE_PLCBackgroundFunction", },
   // example function to be called before every PLC "Fast" cycle
   { LLFUNCT_IO,       (void *)beforeIOFunction,       "EXAMPLE_beforeIOFunction", },
   // example function to be called after every PLC "Fast" cycle
   { LLFUNCT_POST_IO,  (void *)afterIOFunction,        "EXAMPLE_afterIOFunction", },
   // example function to be called for specific events
   { LLFUNCT_RTNOTIFY, (void *)notifyFunc,             "EXAMPLE_notifyFunc", },
};

This section defines the function that the plugin exposes to the runtime, and the way the runtime have to handle them. The behaviour is defined in the first field:

  • LLFUNCT_IO: is an I/O function, called every PLC cycle, just before PLC execution.
  • LLFUNCT_INIT_L0: is an initialization function, indeed the function initFunction asks the runtime the actual physical address of data blocks and initializes Spi communication.
  • LLFUNCT_PLC: defines a function that can be called from inside the PLC code
  • LLFUNCT_BACKGND: defines a function to be called every background PLC run

Configure runtime: LLExec.conf

Once you have build the plugin, copy it in the directoy C:\Program Files (x86)\Axel PC Tools\LLExec or where you have installed the runtime.
Now you have to edit the file LLExec.conf

<?xml version="1.0"?>
<llexecconfig overtime_check="0" defaultRetainMemoryService="MemoryBackUpFile">
 <runtimes>
  <runtime filename="LLXRt_x86_PLC.dll" area="0" targetId="LLExec_x86_2p0" targetComm="LLEXECPLC">
 ...
 ...
 <plugins>
  <plugin filename="LLXPlugin_MemoryBackUpFile.dll"/>
  <plugin filename="LLModbusTCP.dll"/>
  <plugin filename="LLModbusRTU.dll"/>
  <plugin filename="LLCANOpen.dll"/> 
  <plugin filename="LLXPlugin_Database.dll"/>
  <plugin filename="LLModbusSlave.dll"/>
  <plugin filename="LLXPlugin_FileSystem.dll"/>
  <plugin filename="LLXPluginSoftScope.dll"/>
  <plugin filename="LLXPlugin_Serial.dll"/>
  <plugin filename="LLXPlugin_Alarms.dll"/>
  <plugin filename="LLXPlugin_Recipes.dll"/>
  <plugin filename="LLXPlugin_LLSymbols.dll"/>
  <plugin filename="LLXPlugin_EXAMPLE.dll"/>
 </plugins>
</llexecconfig>


This is the configuration file of LLExec, you can see our modification in the section plugins:

  • we have inserted the plugin LLXPlugin_EXAMPLE.dll in order to load it at startup.

Now you can run the LLExec process.

Edit PLL library

Together with the plugin sources you can find the file named EXAMPLE.PLL which was generated from LogicLab, but is a simple text file, and is a type of library that can be linked in a LogicLab project.
A better way to generate a library is to use the automatic library generation in LogicLab, please consult the chapter "CUSTOM WORKSPACE OPERATIONS" in LogicLab manual, and the article https://www.axelsw.it/pwiki/index.php/Library_Generator
The library has two sections

VAR_GLOBAL
   {G:"EXAMPLE_vars"}
   exampleDataBlock    AT %MD5.0   : ARRAY[ 0..99 ] OF DINT;
   internalDataBlock   AT %ID6.0   : ARRAY[ 0..9 ]  OF REAL;
   digitalInputs       AT %IX10.0  : ARRAY[ 0..15 ] OF BOOL;
   digitalOutputs      AT %QX10.0  : ARRAY[ 0..15 ] OF BOOL;
END_VAR

in this first section the data blocks that we have exposed in the plugin are defined.
After AT we have the IEC 61131 syntax of logical memory addresses, for example %MD5.0 means:

  • M => Memory (type of data block), we also have I for input and Q for outputs
  • D => Doubleword (size of data block element)
  • 5. => The previously defined data block index
  • 0 => The offset of the first element of array


The second section shows how we can define embedded function that can be called from inside the PLC code:

FUNCTION EXAMPLE_PLCFunction : DINT
   VAR_INPUT
      par : DINT;
   END_VAR
   {CODE:EMBEDDED}
END_FUNCTION

Note that in IEC 61131 standard every function must have one or more input parameters, and a return value.

Access from LogicLab

Now that we have a PLC runtime with the EXAMPLE plugin running, we can test it with LogicLab. Download the last Automation Suite from the Axel website, download section and install it on your PC.
Open LogicLab, choose "New project", give it a name, and select target LLExec PLC runtime (x86) 2.0.

Open the dialog from the menu On-line==>Set up communication...
Select GDB protocol and press the Properties button
In the new window insert the IP address of the PC running LLExec (ie 127.0.0.1) and make shure the port is set to 5000.

Now you have to connect to your LLExec before any other operation: this because LogicLab must update his knowledge about the target, that is changed because of the new plugin. LogicLab will store the new configuration in the project file.
To connect select from the menu On-line==>Connect.

Select from menu Project==>Library manager
Press button Add then select the EXAMPLE.pll library from the filesystem.

Now, from the project, you can write data and read data from two arrays exampleDataBlock and internalDataBlock.
You can also call the function EXAMPLE_PLCFunction from a PLC Program.

Further readings

For more information about the use of LogicLab you can refer to the User manual, you can read it from the Help menu.