LLExec Dev: Difference between revisions

From Axel Public Wiki
Jump to navigation Jump to search
(Created page with "=Summary= The purpose of this page is to allow expert Raspberry PI developers to extend the PLC runtime.<br> It shows up how the interface to PiFace I/O board can be implement...")
 
 
(23 intermediate revisions by the same user not shown)
Line 1: Line 1:
=Summary=
=Summary=
The purpose of this page is to allow expert Raspberry PI developers to extend the PLC runtime.<br>
The purpose of this page is to allow expert developers to extend the LLExec PLC runtime.
It shows up how the interface to PiFace I/O board can be implemented from scratch.<br>
You can clone the example to implement an inteface with your own I/O module.


=Prerequisites=
=Prerequisites=
To follow this guide you need:
To follow this guide you need:
* A Raspberry PI with a running and licensed PLC runtime ('''LLExec''' >= 2.6.2 is suggested).
* 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
You can install the runtime following the guide in [[Raspberry Pi]]
* PluginHeaders SDK '''that must match''' the LLExec version; please ask this package to support@axelsw.it
* A Raspbian cross-compile toolchain
* Microsoft Visual Studio 2022
You can download it from https://github.com/raspberrypi/tools


=Setup your Linux machine=
=Setup your Windows machine=
Uncompress the toolchain and find the path of the compiler tools: for example suppose they are under the folder
Create a directory and uncompress the PluginHeaders in it. Plugin headers expose the LLExec API to interface with the runtime.<br>
/home/axel/toolchain_RPI_orig/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin
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.
In this case you have to edit your '''.profile''' file and add the row
export PATH=/home/axel/toolchain_RPI_orig/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin:$PATH
in order to make the compiler available to the system.<br>
At this point create a directory and uncompress '''[http://www.axelsw.it/pwiki/pub/PluginHeaders_2.6.2.zip PluginHeaders_2.6.2.zip]''' in it. You can download the file from our site, in the Dowload section.<br>
Plugin headers expose the LLExec API to interface with the runtime, they require that some defines are set in order to compile correctly. As you will see after, these defines will be already defined in the Makefile.<br>
Now download and uncompress the file '''[http://www.axelsw.it/pwiki/pub/LLXPlugin_PIFACE.zip LLXPlugin_PIFACE.zip]''' in the previous foldere where you have already uncompressed the plugin headers.<br>
Please note that every release of LLExec has its own plugin headers, so be shure 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=
=Compile the plugin=
In order to compile the plugin, go to the directory you have previously uncompressed, ie
Please open with Visual Studio the project
  /home/axel/plugin_headers_root/LLXPlugin_PIFACE
  C:\plugin_headers_root\LLXPlugin_EXAMPLE\LLXPlugin_EXAMPLE.vcxproj
type
Please compile it.
make
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).
If you have done correctly all the previous steps, this will generate the file '''LLXPlugin_PIFACE.so'''.
 
Copy this file on the RaspberryPI in the same directory where the runtime is installed.
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=
=Plugin concepts=
Line 38: Line 31:
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.<br>
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.<br>
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.<br>
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.<br>
In the next chapters you will see the detail of the plugin PIFACE, and the details of the ''pll'' library that exposes the plugin features.<br>
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.<br>
At last you have to modify LLExec configuration file '''LLExecLinux.conf''' in order to run your plugin.
At last you have to modify LLExec configuration file '''LLExec.conf''' in order to run your plugin.


=Plugin details=
=Plugin details=
The plugin LLXPlugin_PIFACE is a subset of what we have implemented to manage Raspberry PI I/O, and it's designed to drive the PiFace expansion, that is driven by the Raspberry SPI interface.<br>
The plugin LLXPlugin_EXAMPLE is an example of the common features that can be implemented in a plugin.<br>
The plugin is composed by several files:
The plugin is composed by several files:
* '''Makefile''': that cross compiles the plugin, and also defines some macros that selects some configuration in the headers. Please leave these defines as they are.
* Project files for many platforms.
* '''LLXPlugin_PIFACE.cpp''': this file contains all the interfaces with LLExec core, in here are defined data blocks and functions to be called.
* '''LLXPlugin_EXAMPLE.cpp''': this file contains all the interfaces with LLExec core, in here are defined data blocks and functions to be called.
* '''io.cpp''' and '''io.h''': these are the sources implementing the spi communication, look at these if you want to interact with PiFace board.<br>
The fastest way to implement a plugin is to copy and refactor one plugin already written, like this one.<br>
The fastest way to implement a plugin is to copy and refactor one plugin already written, like this one, or the '''LLXPlugin_EXAMPLE''' included in the plugin headers distribution.<br>
Let's do a closer look to LLXPlugin_EXAMPLE.cpp:<br>
Let's do a closer look to LLXPlugin_PIFACE.cpp:<br>
the first section we want to look at is the following
the first section we want to look at is the following
  static LLEXEC_DBDEF m_DataBlocks[] =
  static LLEXEC_DBDEF m_DataBlocks[] =
  {
  {
  { FALSE, DBTY_INP, 0, 0, DB_INPUT_MAX_SIZE, sizeof(bool_t), DBRW_R, },
    // example datablock: it will be %MD5.0, 100 elements of 32bit each, read/write, no procImg, allocated by LLExec 
  { FALSE, DBTY_OUT, 0, 0, DB_OUTPUT_MAX_SIZE, sizeof(bool_t), DBRW_RW, },
    { 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 two datablocks used to expose input and output to PLC.<br>
This defines the datablocks used to expose input and output to PLC.<br>
the first field says that both data blocks don't have an automatic process image<br>
# field says that both data blocks don't have an automatic process image<br>
the second 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<br>
# 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<br>
the third field is the index of the data block, which in turn is part of the logical address<br>
# field is the index of the data block, which in turn is part of the logical address<br>
the fourth field is the physical address of the data block, where zero means that the LLExec core must allocate automatically the memory at any address<br>
# field is the physical address of the data block, where zero means that the LLExec core must allocate automatically the memory at any address<br>
the last fields, self explanatory, are the size in elements, the size of elements, and the read-write access.<br>
# field is the number of elements
<br>
# field is the size of each element
# 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:
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 syncIO()
  static void beforeIOFunction()
  {
  {
  int idx;
   // do data exchange here
  uint8_t byte;<br>
  if (m_ioEnabled)
   m_stateInput = io_read_input();<br>
  byte = m_stateInput;
  for (idx = 0; idx < NUM_OUTPUTS; idx++)
  {
  m_inputs[idx] = byte & 0x1;
  byte >>= 1;
  }<br>
  byte = 0;
  for (idx = NUM_OUTPUTS -1; idx >= 0; idx--)
  {
  if (m_outputs[idx])
    byte |= 1;
  else
    byte &= ~1;<br>
  if (idx)
    byte <<= 1;
  }<br>
  m_stateOutput = byte;
  if (m_ioEnabled)
  io_write_output(m_stateOutput);
  }
  }
<br>
<br>
Line 94: Line 72:
  static LLEXEC_FUNDEF m_Functions[] =
  static LLEXEC_FUNDEF m_Functions[] =
  {
  {
  { LLFUNCT_INIT_L0, (void *)initFunction, "initFunction", }, // init function
    // example system Init function
  { LLFUNCT_IO, (void *)syncIO, "PLCsyncIO", }, // io_function: is called before every PLC cycle
    { LLFUNCT_INIT_L0, (void *)initFunction,           "EXAMPLE_initFunction", },
  { LLFUNCT_PLC, (void *)PIFACE_resetAllOutputs, "PIFACE_resetAllOutputs", }, // plc exported function
    // 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.
This section defines the function that the plugin exposes to the runtime, and the way the runtime have to handle them.
Line 103: Line 92:
* '''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_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_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.<br>
Now you have to edit the file '''LLExec.conf'''


=Configure runtime: LLExecLinux.conf=
Once you have build the plugin, copy it in the directoy '''/data/plc''' or where you have installed the runtime.<br>
Now you have to edit the file '''LLExecLinux.conf'''
  <?xml version="1.0"?>
  <?xml version="1.0"?>
  <llexecconfig overtime_check="0" overtime_threshold="0.9" defaultRetainMemoryService="MemoryBackUpFile" PLCErrorPolicy="stopImmediately">
  <llexecconfig overtime_check="0" defaultRetainMemoryService="MemoryBackUpFile">
<runtimes>
  <runtimes>
  <runtime filename="./LLXRt_ARM_PLC.so" area="0" targetId="RaspPI_1p1" targetComm="RaspPI">
  <runtime filename="LLXRt_x86_PLC.dll" area="0" targetId="LLExec_x86_2p0" targetComm="LLEXECPLC">
  <memory>
  ...
    <data size="0x40000"/>
   ...
    <databit size="0x0"/>
  <plugins>
    <dataret size="0x1000"/>
   <plugin filename="LLXPlugin_MemoryBackUpFile.dll"/>
    <code size="0x400000"/>
   <plugin filename="LLModbusTCP.dll"/>
    <debug size="0x400000"/>
   <plugin filename="LLModbusRTU.dll"/>
  </memory>
   <plugin filename="LLCANOpen.dll"/>  
  <tasks>
  <plugin filename="LLXPlugin_Database.dll"/>
    <task name="Fast" id="0" period="10000" type="io" programmableTaskTime="true"/>
  <plugin filename="LLModbusSlave.dll"/>
    <task name="Slow" id="1" period="20000" type="cyclic" programmableTaskTime="true"/>
  <plugin filename="LLXPlugin_FileSystem.dll"/>
    <task name="Background" id="2" period="100000" type="background"/>
  <plugin filename="LLXPluginSoftScope.dll"/>
    <task name="Boot" id="3" period="0" type="boot"/>
  <plugin filename="LLXPlugin_Serial.dll"/>
    <task name="Init" id="4" period="" type="init"/>
  <plugin filename="LLXPlugin_Alarms.dll"/>
  </tasks>
  <plugin filename="LLXPlugin_Recipes.dll"/>
   </runtime>
  <plugin filename="LLXPlugin_LLSymbols.dll"/>
<! -- <runtime filename="./LLXRt_Linux_ARM_HMI.so" area="1" targetId="RaspPI_1p1" targetComm="RaspPI_HMI">
  '''<plugin filename="LLXPlugin_EXAMPLE.dll"/>'''
  <memory>
  </plugins>
    <data size="0x80000"/>
    <databit size="0x0"/>
    <dataret size="0x0"/>
    <code size="0x800000"/>
    <debug size="0x400000"/>
   </memory>
  <tasks>
    <task name="Draw" id="0" period="20000" type="worker"/>
    <task name="Refresh" id="1" period="20000" type="worker"/>
    <task name="Trend" id="2" period="100000" type="worker"/>
   </tasks>
  <params>
    <param name="vnc_enable" value="false"/>
   </params>
</runtime> -- >
</runtimes><br>
<plugins>
  <plugin filename="./LLXPlugin_MemoryBackUpFile.so">
   <MemoryBackUpFile inhibitTime="20"/>
  </plugin>
  <plugin filename="./LLXPlugin_DefaultKeyboard492x272.so"/>  
  <plugin filename="./LLXPlugin_Database.so"/>
  <plugin filename="./LLXPlugin_FileSystem.so"/>
  <plugin filename="./LLXPlugin_Recipes.so"/>
  <plugin filename="./LLXPlugin_Alarms.so"/>
  <plugin filename="./LLModbusTCP.so"/>
  <plugin filename="./LLXPlugin_ModbusSlave.so"/>
'''''<! --'''''                <plugin filename="./LLXPlugin_RASPPI.so"/>
  <plugin filename="./LLXPlugin_RASPPI_GPIO.so"/> '''''-- >'''''
  '''''<plugin filename="./LLXPlugin_PIFACE.so"/>'''''
</plugins>
<datablocks>
  <!-- NB i datablock input sono stati messi rw per comodità di manipolazione da PLC -->
  <db img="no" type="memo" id="100" elems="20000" datasize="1" access="rw" resetOnRestart="yes"/>
  <db img="no" type="memo" id="101" elems="20000" datasize="1" access="rw" resetOnRestart="yes"/>
  <db img="no" type="memo" id="200" elems="65536" datasize="1" access="rw" resetOnRestart="yes"/>
  <db img="no" type="memo" id="201" elems="4096" datasize="1" access="rw" resetOnRestart="yes" retain="yes"/>
</datablocks>
  </llexecconfig>
  </llexecconfig>
<br>
<br>
This is the configuration file of LLExec,
This is the configuration file of LLExec,
you can see our modification in the section '''plugins''':
you can see our modification in the section '''plugins''':
* we have commented the '''LLXPlugin_RASPPI.so''' and the '''LLXPlugin_RASPPI_GPIO.so''' to prevent conflicts with theese plugins
* we have inserted the plugin '''LLXPlugin_EXAMPLE.dll''' in order to load it at startup.
* we have inserted the plugin '''LLXPlugin_PIFACE.so''' in order to load it at startup.
Now you can run the LLExec process.
Now you can reboot the Raspberry PI, or restart the runtime with the command
/etc/init.d/LLExecDaemon restart


=Edit pll library=
=Edit PLL library=
In the directory of the example there is a file named '''PIFACE.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.
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.<br>
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
<br>
<br>
The library has two sections
The library has two sections
  VAR_GLOBAL
  VAR_GLOBAL
  {G:"Ungrouped_vars"}
    {G:"EXAMPLE_vars"}
   PIFACE_digin AT %IB0.0 : ARRAY[ 0..7 ] OF BOOL;
    exampleDataBlock    AT %MD5.0  : ARRAY[ 0..99 ] OF DINT;
  PIFACE_digout AT %QB0.0 : ARRAY[ 0..7 ] OF BOOL;
    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
  END_VAR
in this first section the two data blocks that we have exposed in the plugin are defined.<br>
in this first section the data blocks that we have exposed in the plugin are defined.<br>
After '''AT''' we have the IEC 61131 syntax of logical memory addresses, for example '''%IB0.0''' means:
After '''AT''' we have the IEC 61131 syntax of logical memory addresses, for example '''%MD5.0''' means:
* '''I''' => Input (type of data block), we also have '''Q''' for outputs
* '''M''' => Memory (type of data block), we also have '''I''' for input and '''Q''' for outputs
* '''B''' => Byte (size of data block element)
* '''D''' => Doubleword (size of data block element)
* '''0'''. => The previously defined data block index
* '''5'''. => The previously defined data block index
* '''0''' => The offset of the first element of array
* '''0''' => The offset of the first element of array
<br><br>
 
 
The second section shows how we can define embedded function that can be called from inside the PLC code:
The second section shows how we can define embedded function that can be called from inside the PLC code:
  FUNCTION PIFACE_resetAllOutputs : BOOL<br>
  FUNCTION EXAMPLE_PLCFunction : DINT
VAR_INPUT
    VAR_INPUT
  dummy : DINT;
      par : DINT;
END_VAR<br>
    END_VAR
  ''{ CODE:EMBEDDED }''<br>
    {CODE:EMBEDDED}
  END_FUNCTION
  END_FUNCTION
Note that in IEC 61131 standard every function must have one or more input parameters, and a return value.
Note that in IEC 61131 standard every function must have one or more input parameters, and a return value.


=Access from LogicLab=
=Access from LogicLab=
Now that we have a PLC runtime with the PIFACE plugin running, we can test it with 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.<br>
Download the last Automation Suite from the Axel website, download section and install it on your PC.<br>
Open LogicLab, choose "New project", give it a name, and select target '''Raspberry Pi 1.1'''.
Open LogicLab, choose "New project", give it a name, and select target '''LLExec PLC runtime (x86) 2.0'''.
<br><br>
<br><br>
Open the dialog from the menu '''On-line'''==>'''Set up communication...'''<br>
Open the dialog from the menu '''On-line'''==>'''Set up communication...'''<br>
Select '''GDB''' protocol and press the '''Properties''' button<br>
Select '''GDB''' protocol and press the '''Properties''' button<br>
In the new window insert the IP address of the Raspberry PI and make shure the port is set to 5000.<br>
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.<br>
<br>
<br>
Now you have to connect to your Raspberry PI 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.<br>
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.<br>
To connect select from the menu '''On-line'''==>'''Connect'''.<br><br>
To connect select from the menu '''On-line'''==>'''Connect'''.<br><br>
Select from menu '''Project'''==>'''Library manager'''<br>
Select from menu '''Project'''==>'''Library manager'''<br>
Press button '''Add''' then select the '''PIFACE.pll''' library from the filesystem.<br>
Press button '''Add''' then select the '''EXAMPLE.pll''' library from the filesystem.<br>
<br>
<br>
Now, from the project, you can write data and read data from the two arrays '''PIFACE_digin''' and '''PIFACE_digout'''.<br>
Now, from the project, you can write data and read data from two arrays '''exampleDataBlock''' and '''internalDataBlock'''.<br>
You can also call the function '''PIFACE_resetAllOutputs''' from a PLC Program, ie in ST language.
You can also call the function '''EXAMPLE_PLCFunction''' from a PLC Program.


=Further readings=
=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.<br>
For more information about the use of LogicLab you can refer to the '''User manual''', you can read it from the '''Help''' menu.<br>
For the datails of the runtime and plugins developement you can read the document [http://www.axelsw.it/pwiki/pub/SP10013-LLExecTechRef.pdf SP10013-LLExecTechRef.pdf].


[[Category:LLExec]]
[[Category:LLExec]]

Latest revision as of 14:33, 9 April 2024

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.