Building a Data Block from the Default Tag Table in TIA Portal

David Krause12 min read
SiemensTIA PortalTutorial / How-to
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

Overview

Engineers frequently maintain their I/O list as a flat collection of PLC tags in the Default tag table of a TIA Portal V18 project, then discover they need those same tags reflected inside a structured data block (DB) — for example, to feed an HMI symbol list, an OPC UA server namespace, or a recipe/array-driven application that consumes a UDT with a Key and a Value field.

This reference covers two distinct engineering paths for an S7-1500 / S7-1514SP-2PN target:

  1. Design-time generation using the TIA Portal Openness API (C# or VB.NET add-in) to read the default tag table and emit a fully-populated DB at compile time.
  2. Runtime iteration using VARIANT pointers, GetSymbolName, symbolic access, and a UDT array in the program itself, so the PLC exposes the live tag list without manual DB authoring.

The two approaches are not mutually exclusive. Openness is the right choice when the tag list changes during engineering and you need the DB to track it. Runtime iteration is the right choice when the same code must run against a varying tag list (e.g., machine options) without re-compiling the project.

Prerequisites

Item Required Value / Notes
TIA Portal V18 (V18.0 + Update 1 or later recommended) with HSPs for S7-1500
PLC CPU S7-1514SP-2PN (Firmware ≥ V2.9 supports optimized block access and full Openness compile target)
TIA Portal Openness Installed via the TIA Portal setup — exposes Siemens.Engineering assembly (DLLs under C:\Program Files\Siemens\Automation\Portal V18\PublicAPI\V18)
.NET runtime .NET Framework 4.8 or .NET 6 (matching the TIA V18 Openness profile)
Visual Studio 2019 / 2022 for add-in authoring
Reference manual SIMATIC S7-1200 Programmable Controller — System Manual (Data Block section)
UDT definition Custom UDT containing two String members (Key, Value) — see UDT Design below
Block access setting matters. When you create the target DB, right-click the data block and select Properties → Attributes → Optimized block access. Optimized access is mandatory for symbolic-only access through Variant and is required by the UDT array members you will populate. Symbolic access via fully-qualified names such as "MyDB".MyUDT[0].Key will fail to resolve at runtime on a non-optimized DB. Reference: TIA Portal documentation — Data Block (DB).

Design-Time Approach — TIA Portal Openness

TIA Portal Openness is the official Siemens automation interface that exposes the engineering framework to external .NET code. It is the only supported way to programmatically read tag tables and create or modify DBs in a TIA V18 project.

Project Object Model (POM) summary for this task

Object Class Path / Property
TIA Project Siemens.Engineering.Project TiaPortal.Projects.Open(...)
PLC Siemens.Engineering.HW.Device (type Plc) Project.Devices
Software (CPU) PlcSoftware PlcDevice.Items.OfType<PlcSoftware>().First()
Tag Table Group PlcTagTableGroup PlcSoftware.TagTableGroups
Default Tag Table PlcTagTable Name == "Default tag table"
Tag PlcTag PlcTagTable.Tags
UDT PlcType PlcSoftware.TypeFolder.Types
Target DB DataBlock (instance DB of UDT or array of UDT) PlcSoftware.BlockGroup.Blocks

Step 1 — Add references

Add the following DLLs from your TIA V18 install to the C# project (use Copy Local = false; they resolve at runtime via the Openness loader):

  • Siemens.Engineering.dll
  • Siemens.Engineering.Hmi.dll (only if cross-targeting HMI)
  • Siemens.Engineering.AddIn.dll

Step 2 — Open the project in exclusive mode

Openness requires the project to be opened with the WithDirectories option and prevents TIA Portal from editing it concurrently.

using Siemens.Engineering;
using Siemens.Engineering.HW;
using Siemens.Engineering.HW.Plc;
using Siemens.Engineering.SW;
using Siemens.Engineering.SW.Blocks;
using Siemens.Engineering.SW.Tags;
using Siemens.Engineering.SW.Types;
using Siemens.Engineering.Compiler;

TiaPortal tia = new TiaPortal(TiaPortalMode.WithoutUserInterface);
Project project = tia.Projects.Open(
    new FileInfo(@"D:\Projects\MyMachine\MyMachine.ap18"));

Step 3 — Locate the Default tag table

PlcSoftware plc = project.Devices
    .OfType<Device>()
    .SelectMany(d => d.Items.OfType<PlcSoftware>())
    .First();

PlcTagTable defaultTable = plc.TagTableGroups
    .SelectMany(g => g.TagTables)
    .First(t => t.Name == "Default tag table");

Step 4 — Enumerate the tags and build the DB payload

var rows = new List<(string Key, string Address, string DataType)>();
foreach (PlcTag tag in defaultTable.Tags)
{
    string dataType = tag.DataTypeName;             // e.g. "Bool", "Real", "Int"
    string logical  = tag.LogicalAddress?.ToString() ?? ""; // e.g. "%I0.0", "%ID4"
    rows.Add((tag.Name, logical, dataType));
}

Step 5 — Create or overwrite the target DB

The cleanest pattern is an instance DB that is an array of a custom UDT. Define the UDT once manually:

TYPE "UDT_TagEntry"
VERSION : 1.0
  STRUCT
    Key   : String[128];   // PLC tag name
    Value : String[64];    // string-formatted current value
    Addr  : String[16];    // logical address e.g. %I0.0
    Type  : String[16];    // BOOL / REAL / INT ...
  END_STRUCT;
END_TYPE

Then in Openness, delete the previous DB and create a new array instance sized to rows.Count:

PlcType udt = plc.TypeFolder.Types.First(t => t.Name == "UDT_TagEntry");

// remove an old DB with the same name, if present
var oldDb = plc.BlockGroup.Blocks
    .OfType<DataBlock>()
    .FirstOrDefault(b => b.Name == "DB_TagList");
if (oldDb != null) oldDb.Delete();

DataBlock db = plc.BlockGroup.Blocks
    .CreateInstanceDB("DB_TagList", udt, rows.Count, true /* retain */);
db.IsOptimizedBlockAccess = true;
Why an array? A UDT instance DB of a single UDT gives you one row. To hold N tag entries you either create an array of UDT (clean, type-safe, recommended) or a single DB with N struct members generated programmatically — possible with Openness but verbose. The array-of-UDT approach is the standard pattern documented in the S7-1200 system manual — Data Block (DB).

Step 6 — Initialize the static fields (Key / Addr / Type)

The Value field is filled at runtime. Key, Addr, and Type are known at design time and can be written through Openness by setting default attribute values via the instance DB's Attributes collection:

// For each UDT member, Siemens.Engineering exposes the
// attribute "Value" that becomes the default in the DB
// instance. Iterate the members and set per-index values.
var udtMembers = udt.Members; // PlcTypeMember collection
for (int i = 0; i < rows.Count; i++)
{
    var entry = rows[i];
    // member-level defaulting through IEngineeringInstance path;
    // in practice most teams generate an SCL source instead
}

For maintainability, generating an SCL source file is more robust than fiddling with attribute defaults. Have Openness emit the equivalent of:

DATA_BLOCK "DB_TagList"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
NON_RETAIN
  STRUCT
    Array : ARRAY[0..N-1] OF "UDT_TagEntry";
  END_STRUCT;
END_DATA_BLOCK

…then write a one-shot FC_InitTagList in SCL that runs once on OB100 / startup and walks the array copying each PLC tag's Key, Addr, and type-name string.

Step 7 — Compile and load

Compiler compiler = plc.GetService<Compiler>();
CompilerResult result = compiler.Compile();
foreach (var msg in result.Messages)
{
    Console.WriteLine($"[{msg.Severity}] {msg.Path}: {msg.Message}");
}
result.Messages.ForEach(m => { /* fail on Error */ });

Step 8 — Save and close

project.Save();
project.Close();
tia.Dispose();

Runtime Approach — Variant + Symbolic Access

If you cannot (or do not want to) re-compile the project each time the tag list changes, you can perform the iteration in SCL on the CPU using VARIANT pointers and a CASE on the data type. The PLC code does not know the tag table, but it does know every tag you hand it symbolically.

Runtime UDT design

TYPE "UDT_TagEntry_RT"
VERSION : 1.0
  STRUCT
    Key   : WString[128];  // symbolic tag name (WString survives any HMI charset)
    Value : WString[64];   // formatted value as text
  END_STRUCT;
END_TYPE

Helper FC — TagToString (Variant in, String out)

FUNCTION "FC_TagToString" : Void
{ S7_Optimized_Access := 'TRUE' }
VERSION : 1.0
VAR_INPUT
    pTag     : Variant;            // ANY pointer to the live tag
END_VAR
VAR_IN_OUT
    sValue   : WString;            // output text
END_VAR
VAR_TEMP
    b   : Bool;
    i   : Int;
    di  : DInt;
    r   : Real;
    s   : String;
    ws  : WString;
END_VAR
BEGIN
    CASE TypeOf(pTag) OF
        TypeOf(b):
            b := pTag;
            sValue := b ? WSTRING#'TRUE' : WSTRING#'FALSE';
        TypeOf(i):
            i := pTag;
            WSTRING_TO_INT_FORMAT(i, sValue);   // manual INT_TO_WSTRING inline
        TypeOf(di):
            di := pTag;
            WSTRING_TO_DINT_FORMAT(di, sValue);
        TypeOf(r):
            r := pTag;
            REAL_TO_WSTRING(r, sValue);
        TypeOf(s):
            s := pTag;
            sValue := WSTRING#'';
            // String to WString via block conv if needed
        ELSE
            sValue := WSTRING#'<unsupported>';
    END_CASE;
END_FUNCTION

WSTRING_TO_*_FORMAT shown above are illustrative — implement with the standard SCL conversions: INT_TO_WSTRING, DINT_TO_WSTRING, REAL_TO_WSTRING (S7-1500 supports these natively). See the S7-1500 system manual, section Type conversion functions for character and string types.

Helper FC — Capture tag name

Siemens provides GetSymbolName for blocks and GetSymbolPath for instance paths, but there is no runtime API to discover tag-table entries from the PLC. The variant input's symbolic name, however, is accessible at runtime only if you reference the tag by a fully-qualified name in the VARIANT. To capture it, pass the name in explicitly:

// Caller side
"DB_TagList".Array[i].Key   := '"MyTag_1"';   // literal string of the tag name
"DB_TagList".Array[i].Value := "FC_TagToString"(
                                   pTag := "MyTag_1",
                                   sValue => "DB_TagList".Array[i].Value);

This is the fundamental reason most teams prefer the Openness design-time approach: the runtime approach works but the caller must hard-code the tag name once per row, which defeats the goal of "iterating over the tag list" dynamically.

Hybrid Approach — Openness-Generated Index, Runtime Read

The practical pattern that solves the user's original requirement is a hybrid:

  1. Openness reads the default tag table at design time and emits a generated ARRAY[0..N-1] OF "UDT_TagEntry" with the Key, Addr, Type filled in.
  2. The runtime DB also exposes a shadow array of VARIANT pointers (one per row) that the Openness script populates by writing the absolute symbolic path into a POINTER TO BYTE or, more practically, by emitting an SCL FC that copies each tag into a typed shadow array element:
// Generated by Openness, one statement per default-tag-table row
"DB_TagList".Shadow[0] := "Tag_1";
"DB_TagList".Shadow[1] := "Tag_2";
"DB_TagList".Shadow[2] := "Tag_3";
// ...

The Shadow array's element type is the broadest data type you need (commonly DWord for bit-packed reads, or Variant for fully generic access — note that Variant cannot be the element of an array in classic S7-1500 firmware < V2.9; use a wrapper block or a typed shadow of the largest type).

Runtime sweep over the hybrid DB

// OB1 cyclic scan
FOR i := 0 TO "DB_TagList".UpperBound DO
    "FC_TagToString"(
        pTag   := "DB_TagList".Shadow[i],
        sValue => "DB_TagList".Array[i].Value);
END_FOR;

Because the symbolic addresses were generated at compile time, the optimizer folds them into direct memory accesses; the loop overhead is minimal.

XML Import Format for the Default Tag Table

The user's project uses an XML file to seed the default tag table. The TIA V18 tag-table XML schema is documented in the Openness help; a minimal valid fragment:

<?xml version="1.0" encoding="utf-8"?>
<TagTable>
  <Name>Default tag table</Name>
  <Tags>
    <Tag>
      <Name>Tag_1</Name>
      <DataType>Bool</DataType>
      <LogicalAddress>%I0.0</LogicalAddress>
    </Tag>
    <Tag>
      <Name>Tag_2</Name>
      <DataType>Real</DataType>
      <LogicalAddress>%ID1</LogicalAddress>
    </Tag>
  </Tags>
</TagTable>

Import path: In TIA Portal, right-click the Default tag table → Import → select the XML. To script the import, Openness provides PlcTagTable.Import(FileInfo, ImportOptions). See the TIA Portal Openness reference manual under PLC tag table import/export.

Verification

Step What to check Pass criterion
1. Compile Openness compile output No Error-severity messages; only Info/Warning tolerated
2. Download CPU transitions to RUN with the new DB SF LED off; diagnostic buffer clean
3. Watch table Open a watch table on DB_TagList All Key entries equal the tag-table names; Type matches
4. Live update Toggle an input that maps to Tag_1 DB_TagList.Array[0].Value flips between 'TRUE' and 'FALSE'
5. HMI / OPC UA Browse the DB through the integrated OPC UA server Each array element is visible as a structured node with Key / Value strings
6. Round-trip Re-run the Openness tool against a modified XML DB regenerates; project compiles; previous DB contents are overwritten without orphan members

Troubleshooting Matrix

Symptom Likely Cause Fix
CompilerResult reports Symbol not found for the generated DB UDT was renamed after the Openness script was generated Recreate the UDT before running Openness, or detect by name in the script
Download fails with "Protected block access" DB created without optimized access Set db.IsOptimizedBlockAccess = true in the script before compile
Runtime access in HMI shows all zeros Target DB not set as Visible in HMI Open DB Properties → Attributes → Accessible from HMI / OPC UA = true
VARIANT access returns 0 Tag address has not been initialized; I/O area is not updated Verify hardware configuration and I/O mapping for the input tag
Openness throws TiaPortalException: project is locked TIA Portal GUI is open against the same project Close the GUI or use TiaPortalMode.WithUserInterface with OpenProjectInProcess properly disposed
Duplicate name on regeneration Old DB not deleted Wrap DB creation in a "delete-if-exists" helper as shown in Step 5
REAL_TO_WSTRING shows scientific notation Default conversion format Use SPRINTF-style formatting via the FORMAT library or post-process the WString

Best Practices and Field Notes

  • Optimized block access is non-negotiable for any DB consumed through symbolic access from the OPC UA server or HMIs. The S7-1200 system manual — Data Block (DB) shows the property dialog explicitly.
  • Keep the UDT small and flat. Nested UDTs work but make the Openness walk more complex and slow down HMI refresh.
  • Version the generated DB with a header comment containing the source XML file name and SHA-256 hash, so audit trails can correlate PLC content with engineering source.
  • Run Openness from CI (Jenkins / Azure Pipelines) and fail the build on Error-severity compile messages. This prevents a stale tag table from silently mismatching the DB.
  • Do not iterate the default tag table at runtime. The S7 CPU has no runtime API that enumerates tag-table contents. The tag table is a project-level artifact; iteration must happen at design time via Openness.
  • Use WString rather than String for any tag the OPC UA server will publish. The integrated OPC UA server on the S7-1500 maps WString directly to String and avoids ASCII-to-UTF-8 mismatches with non-Latin tag names.
  • Pin the Openness DLL versions to your TIA V18 install path. Mixing V17 and V18 Siemens.Engineering.dll references causes the loader to fail with FileLoadException.
  • Array bounds: when generating an array of UDT, prefer ARRAY[0..N-1] to match the zero-based indexing used by SCL FOR loops; this prevents off-by-one errors in the runtime sweep.

Related Standards and References

Can I iterate the Default tag table from inside the PLC at runtime?

No. The PLC has no API that exposes the contents of the default tag table at runtime. The tag table is a project-level artifact compiled away after build. Use TIA Portal Openness to read the tag table at design time, or pass symbolic tag names into a Variant array manually.

Do I need TIA Portal Openness to import an XML file into the Default tag table?

No — the GUI Import command (right-click the Default tag table → Import) handles the XML natively in TIA Portal V18. Openness is only required if you want to drive the import from a script, regenerate DBs, or wire the tag import into a CI pipeline.

What UDT member types should I use for Key and Value?

Use WString[128] for the Key (tag name) and WString[64] for the Value (formatted text). WString maps cleanly to the integrated OPC UA server and avoids encoding issues when tag names contain non-ASCII characters from international projects.

Why does my HMI or OPC UA client see only zeros in the new DB?

The DB is most likely not marked Accessible from HMI / OPC UA. Open the DB in the project tree, select Properties → Attributes, and enable Accessible from HMI / OPC UA. Also confirm that Optimized block access is enabled — see the S7-1200 manual — Data Block (DB).

Which TIA Portal version introduced full support for array-of-UDT DBs in Openness?

Array-of-UDT instance DBs are supported from TIA Portal V14 SP1 onward. For TIA V18 on a 1514SP-2PN, both the Openness API call CreateInstanceDB(name, udt, count, retain) and optimized block access work without restrictions on firmware ≥ V2.9.

Back to blog