Use the ContainerLib when you need to access a larger set of data repetitively and with maximum update speed. For example, input data you want to read every machine cycle. Using the function mlpiContainerCreate, you first have to create a container by naming all the elements you want to read with the container. This also defines the memory layout of the container. After that, you can read/write your data using the mlpiContainerUpdate function. When the container is no longer needed, delete it using mlpiContainerDestroy.
- Attention
- The addresses of the variables of the container content might change on every download, reset origin or online change of the PLC application, so you have to stop and destroy each regarding container before you load or change the PLC application!
-
Please ensure you don't destroy a container during an access on it (update, read information e.g.)!
A container is a list of items which are described as so called 'tags'. Each 'tag' identifies a data element which can be read or written. For example, a symbolic variable from the PLC or data from the input area of the I/O mapping.
The first argument of a tag always defines the type of data to read or write. The following data sources are available:
- LOGICLIB_MEMORY_AREA: Accessing Input, Output and Marker area of PLC (%I, %Q, %M).
- LOGICLIB_SYMBOL: Accessing variables and arrays of PLC by symbolic name using symbolic variables. Access requires a symbol configuration of desired variables.
- IOLIB_FIELDBUS_IO: Accessing fieldbus IO data directly from fieldbus driver. Fixed spelling error (notice the 'd'). Needs Server-Version newer than 1.5.0.0.
- IOLIB_FIELBUS_IO: Accessing fieldbus IO data directly from fieldbus driver. Compatible tag with spelling error for Server-Verion 1.0.0.0 and newer. Still working in newer versions. Use if you want to access also controls older than 13V12.
- ALIGNMENT_DUMMY: Dummy elements to align the subsequent tag.
- LOGICLIB_MEMORY_AREA
- argument 1: application name. (e.g. "Application")
- argument 2: area. (Either "INPUT", "OUTPUT" or "MARKER")
- argument 3: bit offset. (e.g. "0" to read from start offset. On bit access any offset value is valid, on byte access the offset must be a multiple of 8.)
- argument 4: bit length. (Note: Single bit access (1) or byte access (8*n) supported. e.g. "1", "8", "16", "24", ... bit.)
- LOGICLIB_SYMBOL
- argument 1: symbol name. (e.g. "Application.PlcProg.boDummy")
- IOLIB_FIELDBUS_IO
- argument 1: master name. (e.g. "Onboard_I_O"). The name of the fieldbus master is the name of the regarding master node in your IndraWorks project. You can also retrieve the list of configured master names by using the function mlpiIoReadFieldbusMasterList.
- argument 2: slave address. (e.g. "1");
- argument 3: area. (Either "INPUT" or "OUTPUT")
- argument 4: bit offset. (e.g. "0" to read from start offset. On bit access any offset value is valid, on byte access the offset must be a multiple of 8.)
- argument 5: bit length. (Note: Single bit access (1) or byte access (8*n) supported. e.g. "1", "8", "16", "24", ... bit.)
- ALIGNMENT_DUMMY
- argument 1: byte length (Note: Only "1", "2", "4" or "8" byte supported.)
- Example:
{
L"LOGICLIB_MEMORY_AREA,Application,MARKER,0,32;"
L"LOGICLIB_MEMORY_AREA,Application,MARKER,32,128;"
L"LOGICLIB_MEMORY_AREA,Application,INPUT,32,32;"
L"IOLIB_FIELBUS_IO,Onboard_I_O,0,INPUT,0,8;"
L"ALIGNMENT_DUMMY,1;"
L"ALIGNMENT_DUMMY,2;"
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varUint;"
L"ALIGNMENT_DUMMY,2;"
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varReal;"
};
Let's say we have the following data in the 'UserVarGlobal' variable list of our application project.
{attribute 'symbol' := 'readwrite'}
{attribute 'linkalways'}
VAR_GLOBAL
varBoolean : BOOL := FALSE;
varString : STRING[31] := 'MLPI Test String';
varUdint : UDINT := 23;
varUdintArray : ARRAY[0..5] OF UDINT := [4, 8, 15, 16, 23, 42];
varReal : REAL := 42.0;
varRealArray : ARRAY[0..5] OF REAL := [4, 8, 15, 16, 23, 42];
END_VAR
Now let's imagine, we want to read all of the data and also the first 4 bytes of the input area (%IX0.0 to %IX3.7). The corresponding tagList for your C/C++ application would look like this:
{
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varString;"
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varUdint;"
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varUdintArray;"
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varBoolean;"
L"ALIGNMENT_DUMMY,1;"
L"ALIGNMENT_DUMMY,2;"
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varReal;"
L"LOGICLIB_SYMBOL,Application.USERVARGLOBAL.varRealArray;"
L"LOGICLIB_MEMORY_AREA,Application,INPUT,0,32;"
};
- Note
- The layout of the different elements and your container is free. You don't need to have the same variable order as in the PLC. Use the alignment elements to align the elements to have better and faster access from within your C/C++ application.
Having defined our tagList, the next step is to create a container for reading by using the function mlpiContainerCreate. A container can either be used for reading or for writing. Not for both! You need to create two containers if you want to read and write the same set of data. In this example, we only create a read container using the following piece of code.
memset(&hContainer, 0, sizeof(hContainer));
printf("\ncall of MLPI function failed with 0x%08x!", (unsigned)result);
return result;
}
printf("\nContainer created. Size in bytes: %d", containerSize);
After we have created the container, we can use the function mlpiContainerUpdate to read the current data of the container elements. This should be really fast now, as the MLPI has cached some information to provide a really fast mechanism here. Call the update function whenever you need to refresh the data.
memset(containerData, 0, containerSize);
printf("\ncall of MLPI function failed with 0x%08x!", (unsigned)result);
delete [] containerData;
return result;
}
Now let's dump the content of the container data we've just read:
printf("\ndumping container data:\n");
utilHexdump(containerData, containerSize);
This results in the following output:
We can see the requested data elements as described in the tagList.
For easier access in your application, you can build a struct with memory maps to your requested data. In the example above, a struct would look like this. You may want to place this in a header file.
#if defined(_MSC_VER)
#pragma pack(push,1)
#define PACKED
#elif defined(__GNUC__)
#define PACKED __attribute__ ((__packed__))
#endif
struct ContainerMap
{
UCHAR memoryAreaInput[4];
} PACKED;
#if defined(_MSC_VER)
#pragma pack(pop)
#undef PACKED
#elif defined(__GNUC__)
#undef PACKED
#endif
Now you can cast your buffer with the container data to the structure and have easy access to the data:
if (sizeof(ContainerMap) != containerSize) {
printf("\ncontainerSize doesn't match structure! Incorrect structure of container layout?!");
return -1;
}
struct ContainerMap *containerMap = (struct ContainerMap*) containerData;
printf("\nvarString: %s", containerMap->varString);
printf("\nvarUdint: %u", containerMap->varUdint);
printf("\nvarUdintArray: %u %u %u %u %u %u"
, containerMap->varUdintArray[0], containerMap->varUdintArray[1], containerMap->varUdintArray[2]
, containerMap->varUdintArray[3], containerMap->varUdintArray[4], containerMap->varUdintArray[5]);
printf("\nvarBoolean: %s", containerMap->varBoolean ? "TRUE" : "FALSE");
printf("\nvarReal: %f", containerMap->varReal);
printf("\nvarRealArray: %f %f %f %f %f %f"
, containerMap->varRealArray[0], containerMap->varRealArray[1], containerMap->varRealArray[2]
, containerMap->varRealArray[3], containerMap->varRealArray[4], containerMap->varRealArray[5]);
This also enables debugger support in your development environment.
- Note
- The ContainerLib functions trace their debug information mainly into the module MLPI_CONTAINER_LIB and in additional into the modules MLPI_ACCESSOR and MLPI_BASE_MODULES. For further information see also the detailed description of the library TraceLib and the notes about Using the Trace for debugging.