LLExec Dev: Difference between revisions

From Axel Public Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 28: Line 28:
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, (uint32_t)&m_internalVar, 10, sizeof(float32_t), DBRW_R, },
  };
  };
This defines the two datablocks used to expose input and output to PLC.<br>
This defines the two datablocks used to expose input and output to PLC.<br>
Line 55: Line 56:
  static void syncIO()
  static void syncIO()
  {
  {
  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 84: Line 63:
  static LLEXEC_FUNDEF m_Functions[] =
  static LLEXEC_FUNDEF m_Functions[] =
  {
  {
  { LLFUNCT_INIT_L0, (void *)initFunction, "initFunction", }, // init function
   { LLFUNCT_IO, (void *)syncIO, "PLCsyncIO", }, // io_function: is called before every PLC cycle
   { LLFUNCT_IO, (void *)syncIO, "PLCsyncIO", }, // io_function: is called before every PLC cycle
   { LLFUNCT_PLC, (void *)PIFACE_resetAllOutputs, "PIFACE_resetAllOutputs", }, // plc exported function
  // example system Init function
  { LLFUNCT_INIT_L0, (void *)initFunction, "initFunction", },
  // example PLC function
   { LLFUNCT_PLC, (void *)exampleFunction, "exampleFunction", },
  // example function to be called every background PLC run
  { LLFUNCT_BACKGND, (void *)backgroundFunction, "PLCBackgroundFunction", },
  };
  };
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 93: Line 76:
* '''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: LLExecLinux.conf=
=Configure runtime: LLExec.conf=
Once you have build the plugin, copy it in the directoy '''/data/plc''' or where you have installed the runtime.<br>
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'''
Now you have to edit the file '''LLExecLinux.conf'''

Revision as of 17:26, 11 December 2017

Summary

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

Prerequisites

To follow this guide you need:

  • A Windows PC with a running and licensed PLC runtime (LLExec >= 2.5.1 is suggested).
You can download and install the runtime from site axelsw.it under the section Downloads.
  • Microsoft Visual Studio 2013.

Setup your Windows machine

Create a directory and uncompress PluginHeaders_2.5.1.zip 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 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

Please open with Visual Studio 2013 the project

C:\plugin_headers_root\LLXPlugin_EXAMPLE\LLXPlugin_EXAMPLE.project

Please compile it. If you have done correctly all the previous steps, this will generate the file LLXPlugin_PIFACE.dll. Copy this file on the your Windows chine in the same directory where the runtime is installedm ie

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

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, (uint32_t)&m_internalVar, 10, sizeof(float32_t), DBRW_R, },
};

This defines the two datablocks used to expose input and output to PLC.
the first field says that both data blocks don't have an automatic process image
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
the third field is the index of the data block, which in turn is part of the logical address
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
the last fields, self explanatory, are the size in elements, the size of elements, and 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 syncIO()
{
  // 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[] =
{
 { LLFUNCT_IO,	(void *)syncIO,		"PLCsyncIO", }, // io_function: is called before every PLC cycle
 // example system Init function
 { LLFUNCT_INIT_L0,	(void *)initFunction,			"initFunction", },
 // example PLC function
 { LLFUNCT_PLC,		(void *)exampleFunction,		"exampleFunction", },
 // example function to be called every background PLC run
 { LLFUNCT_BACKGND,	(void *)backgroundFunction,		"PLCBackgroundFunction", },
};

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 /data/plc or where you have installed the runtime.
Now you have to edit the file LLExecLinux.conf

<?xml version="1.0"?>
<llexecconfig overtime_check="0" overtime_threshold="0.9" defaultRetainMemoryService="MemoryBackUpFile" PLCErrorPolicy="stopImmediately">
<runtimes>
 <runtime filename="./LLXRt_ARM_PLC.so" area="0" targetId="RaspPI_1p1" targetComm="RaspPI">
  <memory>
   
   <databit size="0x0"/>
   <dataret size="0x1000"/>
   
   <debug size="0x400000"/>
  </memory>
  <tasks>
   <task name="Fast" id="0" period="10000" type="io" programmableTaskTime="true"/>
   <task name="Slow" id="1" period="20000" type="cyclic" programmableTaskTime="true"/>
   <task name="Background" id="2" period="100000" type="background"/>
   <task name="Boot" id="3" period="0" type="boot"/>
   <task name="Init" id="4" period="" type="init"/>
  </tasks>
 </runtime>
<! --		<runtime filename="./LLXRt_Linux_ARM_HMI.so" area="1" targetId="RaspPI_1p1" targetComm="RaspPI_HMI">
  <memory>
   
   <databit size="0x0"/>
   <dataret size="0x0"/>
   
   <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>
<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> <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>


This is the configuration file of LLExec, 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_PIFACE.so in order to load it at startup.

Now you can reboot the Raspberry PI, or restart the runtime with the command

/etc/init.d/LLExecDaemon restart

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.
The library has two sections

VAR_GLOBAL
 {G:"Ungrouped_vars"}
 PIFACE_digin AT %IB0.0 : ARRAY[ 0..7 ] OF BOOL;
 PIFACE_digout AT %QB0.0 : ARRAY[ 0..7 ] OF BOOL;
END_VAR

in this first section the two 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 %IB0.0 means:

  • I => Input (type of data block), we also have Q for outputs
  • B => Byte (size of data block element)
  • 0. => 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 PIFACE_resetAllOutputs : BOOL
VAR_INPUT dummy : 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 PIFACE 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 Raspberry Pi 1.1.

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 Raspberry PI and make shure the port is set to 5000.

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.
To connect select from the menu On-line==>Connect.

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

Now, from the project, you can write data and read data from the two arrays PIFACE_digin and PIFACE_digout.
You can also call the function PIFACE_resetAllOutputs from a PLC Program, ie in ST language.

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.
For the datails of the runtime and plugins developement you can read the document SP10013-LLExecTechRef.pdf.