Raspberry Pi Dev
Summary
The purpose of this page is to allow expert Raspberry PI developers to extend the PLC runtime.
It shows up how the interface to PiFace I/O board can be implemented from scratch.
You can clone the example to implement an inteface with your own I/O module.
Prerequisites
To follow this guide you need:
- A Raspberry PI with a running and licensed PLC runtime (LLExec >= 2.6.2 is suggested). You can install the runtime by following the guide in Raspberry Pi and downloading it from our website.
- A Raspbian cross-compile toolchain. You can download it from https://github.com/raspberrypi/tools
- PluginHeaders SDK that you can download from here. IMPORTANT: always make sure that the version of LLExec matches the version of PluginHeaders!
Setup your Linux machine
Uncompress the toolchain and find the path of the compiler tools: for example suppose they are under the folder
/home/axel/toolchain_RPI_orig/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin
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.
At this point create a directory and uncompress PluginHeaders in it. You can download the file from our site, in the Dowload section.
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.
Now download and uncompress the file LLXPlugin_PIFACE.zip in the previous foldere where you have already uncompressed the plugin headers.
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
In order to compile the plugin, go to the directory you have previously uncompressed, ie
/home/axel/plugin_headers_root/LLXPlugin_PIFACE
type
make
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.
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 PIFACE, and the details of the pll library that exposes the plugin features.
At last you have to modify LLExec configuration file LLExecLinux.conf in order to run your plugin.
Plugin details
As an example,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.
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.
- LLXPlugin_PIFACE.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.
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.
Let's do a closer look to LLXPlugin_PIFACE.cpp:
the first section we want to look at is the following
static LLEXEC_DBDEF m_DataBlocks[] = { { FALSE, DBTY_INP, 0, 0, DB_INPUT_MAX_SIZE, sizeof(bool_t), DBRW_R, }, { FALSE, DBTY_OUT, 0, 0, DB_OUTPUT_MAX_SIZE, sizeof(bool_t), DBRW_RW, }, };
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() { int idx; uint8_t byte;
if (m_ioEnabled) m_stateInput = io_read_input();
byte = m_stateInput; for (idx = 0; idx < NUM_OUTPUTS; idx++) { m_inputs[idx] = byte & 0x1; byte >>= 1; }
byte = 0; for (idx = NUM_OUTPUTS -1; idx >= 0; idx--) { if (m_outputs[idx]) byte |= 1; else byte &= ~1;
if (idx) byte <<= 1; }
m_stateOutput = byte; if (m_ioEnabled) io_write_output(m_stateOutput); }
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_INIT_L0, (void *)initFunction, "initFunction", }, // init function { LLFUNCT_IO, (void *)syncIO, "PLCsyncIO", }, // io_function: is called before every PLC cycle { LLFUNCT_PLC, (void *)PIFACE_resetAllOutputs, "PIFACE_resetAllOutputs", }, // plc exported function };
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
Configure runtime: LLExecLinux.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.
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.
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.