Publish PLC variables to an HMI with CODESYS symbol configuration

9 min read · Published November 5, 2026

Add one Symbol Configuration object under your CODESYS V3 application, mark the variables the screen needs, and download; an HMI with a CODESYS-symbolic driver then reads them by name. Keep AT declarations and Modbus registers for the fallback case: a Modbus-only HMI on a controller that documents its IEC-to-Modbus register map.

An HMI shows values that live as IEC variables inside the PLC. In a CODESYS V3 application the clean handover is the Symbol Configuration object: mark the variables the screen needs, download the application, and an HMI whose driver speaks CODESYS symbolic access reads them by name. No register plan to maintain, and a renamed variable costs a re-browse on the HMI side, nothing on the PLC side. This article walks that setup end to end: what to check before you start, the exact steps in the project tree, how the HMI binds tags to names, and the one fallback that still matters, AT declarations mapped to Modbus registers, worked through to the addresses you would type into the tag.

How do you publish PLC variables to an HMI in CODESYS?

You publish screen variables by adding one Symbol Configuration object under your application, marking the variables the screen needs inside it, and letting the HMI's CODESYS-symbolic driver browse them by name; reserve the AT-declaration Modbus route for the rare HMI with no CODESYS driver.

Symbolic access means the client sends a variable name and the controller's runtime resolves it against the published set. The connection itself still runs over the controller's normal communication service, the same one you download the application through, typically USB, serial or Ethernet. The name identifies the value; the transport stays whatever the driver and controller agree on.

Four routes reach the same screen values, and the driver list on the HMI side picks between them:

Route What it needs How the screen references a value
CODESYS visualization on the controller or a panel PC A target that runs the visualization Application variables used directly
HMI panel with a CODESYS driver Data source set to CODESYS symbolic access Tags bound to published names
SCADA package on a PC A SCADA client with a CODESYS connection Items resolved from the published set
HMI with a Modbus driver Controller Modbus slave capability and a printed IEC-to-Modbus map Register numbers fixed by AT declarations

Take the first route your hardware supports, in that order. If the screen is a PC running the CODESYS visualization itself, the article on a CODESYS HMI on a PC when the PLC has no web server walks that setup. The rest of this article is the second route: a separate HMI with its own tool, reading published names.

What do you need before you start?

You need four things: a CODESYS V3 application, an HMI tool whose driver list includes CODESYS symbolic access, the screen's list of values written out as one line each, and, only for the Modbus fallback, a controller with Modbus slave capability and a documented IEC-to-Modbus register map.

  • The CODESYS V3 application open in the project tree, with the target device configured and communicating.
  • The HMI tool's driver list, from its own documentation, with CODESYS symbolic access among the data source types. If the entry is missing, the main route is closed and the Modbus fallback below becomes your route.
  • The screen's value list, one line per value: tag text, variable name, declared type. Publish what the screen reads; a diagnostic variable earns publication only when somebody actually looks at it.
  • For the fallback only: the controller's Modbus slave capability and the manual page that maps IEC locations to Modbus register numbers. No printed map, no fallback.

How do you add the Symbol Configuration object and publish the variables?

You add one Symbol Configuration object under the application and mark the variables from your screen list in its editor, then build and download so the controller's published symbols match the running program.

  1. Write the value list from the HMI drawings, not from the PLC code. A screen with six values gets six published variables, and everything internal stays out.
  2. In the project tree, right-click Application and select Add Object.
  3. Choose Symbol Configuration in the dialog and confirm. The object sits under the application and publishes that application's set, so add it once and maintain it there.
  4. In the editor that opens, mark each variable from your list. A marked name becomes visible to a symbolic client at the next download.
  5. Build the application and download it, so the running program and the published symbol set match.
  6. Treat a later rename or type change as a two-side event: change it in the application, download again, then re-browse the tag on the HMI. A screen can keep showing an obsolete reference while the PLC project still builds clean, which is why the check below watches a value move.

How does the HMI read the variables by name?

The HMI reads by name through a data source set to the CODESYS Symbolic option on its Communication settings, then you browse the published variable names from the HMI tool and bind each screen tag to one symbol, so a renamed tag is a re-browse, never a rewritten address.

Create the data source in the HMI tool, enter the controller's address, go online, and browse: the published variables appear under their declared names with their declared types.

Bind each screen tag to a browsed name, and match types exactly. An INT and a UINT occupy the same 16 bits with different signs, so a temperature that dips below zero displays nonsense on an unsigned tag. String lengths and array sizes travel with the declaration, so the tag inherits them from the browse, never from a guess.

Two failure layers show up right here. A variable missing from the browse list is a publication problem: re-mark it in the Symbol Configuration editor and download again. A name that browses but never moves is a connection or binding problem: check the data source status first, then the tag's reference.

When should you fall back to AT declarations and Modbus registers?

Fall back to AT declarations and Modbus registers in one case: the HMI's best driver is Modbus and the controller has Modbus slave capability with a documented IEC-to-Modbus map, because the AT keyword fixes each variable to an address such as %MD40 that the HMI reads as registers.

The AT keyword fixes an IEC variable to a memory location, and that is all it does; the controller's own Modbus slave function is what serves the registers. The route works only on a device with Modbus slave capability, and only where its documentation prints the map from IEC locations to register numbers. Where both exist, the declarations use the standard IEC address forms:

Declaration Type What the HMI reads
myBool AT %MX0.0 : BOOL; one memory bit a coil or a bit inside a register word, per the map
myInput AT %IX0.0 : BOOL; input image bit the same, from the input area
myOut AT %QX0.0 : BOOL; output image bit the same, from the output area
myWORD AT %MW0 : WORD; 16-bit word one holding register
myReal AT %MD0 : REAL; 32-bit float two consecutive registers, order per the map

The address forms are the standard IEC ones; the variable names are illustrative. Give each live variable its own range: %MD0 spans words %MW0 and %MW1, so a WORD at %MW0 would share bytes 0 to 1 with a REAL at %MD0, and the two would fight over the same bytes.

Bits ride one level down: the map decides whether %MX0.0 arrives as a coil or as a bit inside a word, and the tag tests that bit.

When several controllers feed one Modbus HMI, the article on an HMI as Modbus TCP master to seven CODESYS IPCs scales up the master-side layout. When a register you write keeps snapping back to its previous value, the fix for a Modbus TCP slave register that resets after a write is the next stop.

Recommendation: Publish through Symbol Configuration whenever the HMI's driver list includes CODESYS symbolic access, because tags track names and no register plan has to stay aligned with the code. Reserve AT declarations plus Modbus registers for the HMI whose best driver is Modbus, and only where the controller's manual prints the IEC-to-Modbus map; on that route, lay out non-overlapping address ranges before the first tag is written.

Worked example: how does a mixer temperature land in two Modbus registers?

A 32-bit REAL fills exactly two 16-bit Modbus registers, so a mixer temperature declared rMixerTemp AT %MD40 : REAL; occupies words %MW80 and %MW81, which this example's controller map prints as holding-register addresses 40081 and 40082.

Scenario: a mixer's temperature must appear on a Modbus-only HMI. The controller's manual states its map in one line: holding-register address 40001 + n is word %MWn, and double words follow the IEC layout, so %MDn starts at word 2n. Work the numbers:

  1. IEC layout. %MD40 is double word 40; each double word holds 2 words, so it covers %MW(2 x 40) = %MW80 and %MW81. In bytes: 40 x 4 = 160, and 160 / 2 = 80 words. Same answer both ways.
  2. Map. Word offset 80 becomes 40001 + 80 = 40081; offset 81 becomes 40082.
  3. Width. An IEC REAL is the 32-bit IEEE 754 single type and a Modbus register carries 16 bits, so the value spans 32 / 16 = 2 registers.
  4. Tag. Function code 3, read holding registers, start address 40081, count 2, data type REAL, high word first, because the map's notes place the high word at the lower address.

Proof the order matters: at 24.5 °C the IEEE 754 bits are 0x41C40000. Check it: sign 0, exponent field 131, 131 - 127 = 4, fraction field 0x440000 / 0x800000 = 0.53125, so the value is (1 + 0.53125) x 2^4 = 24.5. The high word is 0x41C4, which the HMI shows as 16836, and the low word is 0x0000, which it shows as 0. Registers 40081 and 40082 reading 16836 and 0 while the PLC holds 24.5 mean the link and the layout are both right. If the tag instead shows a value near zero, about 2.4 x 10^-41, the words arrived swapped (16836 x 2^-149 is about 2.4 x 10^-41), and the fix is one setting: flip the tag's word order to low word first.

The decision for this case: with the map printed and the arithmetic checked, the Modbus route costs two registers and one word-order setting. The same screen served by a CODESYS-symbolic client would have needed no registers at all, only the published name.

How do you check that it worked?

Change one published value in the PLC, set the HMI's scan time for the tag to 500 ms, and expect the screen to follow within about 1 second on a LAN; a stuck value points at a binding or publication problem first.

Run the checks in order:

  • Build and download. The project compiles, the download completes, and the Symbol Configuration editor still shows your marks.
  • Browse. The HMI's data source lists the published names exactly as declared, with types attached.
  • Follow a value. Write 87.5 into the mixer temperature in the PLC, leave the tag's scan time at 500 ms, and the screen should reach 87.5 within one or two polls, about 1 second on a LAN link.
  • Modbus cross-check. On the fallback route, registers 40081 and 40082 read 16836 and 0 while the PLC holds 24.5, per the worked example above.

If the value never moves, split the fault by layer. Missing from the browse list is publication: re-mark the variable in the Symbol Configuration editor and download. Data source offline is connection: address, port and driver settings. Name browses but never moves is binding: the tag holds an obsolete reference, the usual aftermath of a rename or a type change, and the fix is to re-browse and re-bind that one tag.

Questions

Does publishing many variables slow the PLC or the link?

The Symbol Configuration object only defines which variables an external client can address. Traffic follows the HMI's tag list, so publishing 200 variables while the screens read 6 of them moves 6 tags' worth of data per scan. Keep internal variables unpublished anyway, so a client can reach only the values you meant to share.

Can several screens read the same published variables?

Yes. The published set is a single interface under the application, and each screen with a CODESYS-symbolic driver browses the same names and reads the same values. Every screen keeps its own tag list, so one HMI can display a mixer temperature that another never reads.

Do I need CANopen or J1939 to access variables by name?

No. Symbolic access runs over the controller's communication service, the same USB, serial or Ethernet connection you download the application through. CANopen and J1939 are fieldbus protocols for other jobs, and adding one changes nothing about how a client resolves a variable name.

Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.

Keep reading

Build your signAsk for a quote