Configuring Modbus Register Access Types on S7-1200 MB_SLAVE
Modbus is a request/reply protocol built on four data tables: Coils, Discrete Inputs, Input Registers, and Holding Registers. The four-table data model is the foundation of Modbus standard compliance, and it dictates the function codes a master can issue, the access rights a slave must enforce, and the addressing scheme integrators must document for the master's polling table. The Siemens S7-1200 implements the Modbus RTU slave role through the MB_SLAVE instruction in the SIMATIC S7-1200 communication library, and it allows you to bind those four tables to specific memory areas inside a STEP 7 (TIA Portal) data block. The challenge many integrators hit on a first deployment is mapping internal PLC variables to read-only vs. read/write tables so that an external master cannot overwrite values the application considers authoritative.
This reference walks through the Modbus data model, the function codes that drive access semantics, and the TIA Portal configuration of MB_SLAVE so that read-only and read/write regions are enforced on the slave side rather than relying on master politeness.
Modbus Data Model Fundamentals
The Modbus Application Protocol (current revision 1.1b3) defines a process image organized into four reference tables. The tables are logical, not physical: any Modbus device maps its internal data into one of these four buckets and the master reaches them by address range and function code.
| Table | Object Type | Access | Address Range (decimal) | PDU Address Range | Read FC | Write FC |
|---|---|---|---|---|---|---|
| Coils | Single bit | Read / Write | 00001 – 09998 | 0x0000 – 0x270E | 01 | 05, 15 (0x0F) |
| Discrete Inputs | Single bit | Read-only | 10001 – 19998 | 0x0000 – 0x270E | 02 | — |
| Input Registers | 16-bit word | Read-only | 30001 – 39998 | 0x0000 – 0x270E | 04 | — |
| Holding Registers | 16-bit word | Read / Write | 40001 – 49998 | 0x0000 – 0x270E | 03 | 06, 16 (0x10) |
Function code selection is not arbitrary. The function code is what the slave inspects to decide whether a request is permitted against a given table. The eight primary function codes a fully implemented Modbus slave responds to are:
| FC (decimal / hex) | Name | Operates On | Operation |
|---|---|---|---|
| 01 (0x01) | Read Coils | Coils | Read 1–2000 contiguous bits |
| 02 (0x02) | Read Discrete Inputs | Discrete Inputs | Read 1–2000 contiguous bits |
| 03 (0x03) | Read Holding Registers | Holding Registers | Read 1–125 contiguous 16-bit words |
| 04 (0x04) | Read Input Registers | Input Registers | Read 1–125 contiguous 16-bit words |
| 05 (0x05) | Write Single Coil | Coils | Write one bit (0x0000=OFF, 0xFF00=ON) |
| 06 (0x06) | Write Single Register | Holding Registers | Write one 16-bit word |
| 15 (0x0F) | Write Multiple Coils | Coils | Write 1–1968 contiguous bits |
| 16 (0x10) | Write Multiple Registers | Holding Registers | Write 1–123 contiguous 16-bit words |
There is no write function code defined for either Discrete Inputs (table 2) or Input Registers (table 3). A Modbus slave receiving a write request against an address that maps into a read-only table returns exception 02 – Illegal Data Address. Exception 02 is the slave's hard guarantee that read-only data is non-writable, regardless of master behavior.
Why the Four-Table Model Matters for S7-1200 Sizing
On the S7-1200 with the Modbus library instructions, each table is realized by a block of addresses inside a STEP 7 data block. The MB_SLAVE instruction exposes four MB_ADDR_xxx inputs that point to the start of each table. A typical parameter set is shown in the call below.
// MB_SLAVE call (typified)
"MB_SLAVE_DB"(
MODE := 1, // 1 = half-duplex (RS485) slave
MB_ADDR := 1, // Modbus station address 1..247
BAUD := 9600,
PARITY := 0, // 0=none, 1=even, 2=odd
RESP_TO := 1000, // response timeout in ms
MB_DB := "ModbusData", // user data DB
HOLD_REG := "ModbusData".HR, // start of holding register area
COIL_REG := "ModbusData".CR, // start of coil area
INPUT_REG := "ModbusData".IR, // start of input register area
INPUT_COIL := "ModbusData".IC, // start of discrete input area
DONE := M10.0,
ERROR := M10.1,
STATUS := MW12,
NDR := M10.2,
DR := M10.3);
Each pointer argument resolves to a ANY-style byte address whose first word is the start offset within MB_DB and whose length is implicit in the size of the variable. The order and location of these structures inside MB_DB is not constrained other than by non-overlap: a coil starting at byte 0 cannot share bytes with an input register starting at byte 0, because the four tables must be addressable independently.
Prerequisites
Before configuring access semantics, validate the following.
-
Firmware. S7-1200 CPU firmware 4.0 or later for the Modbus RTU library; firmware 4.2 or later for the extended Modbus TCP variant (
MB_CLIENT/MB_SERVER). Confirm in the TIA Portal "Online & Diagnostics" → "Module Information" view. - CM/CB or onboard RS485. The Modbus RTU slave role requires a serial interface. The S7-1211/1212/1214 CPUs include an onboard RS485 port (port 0). For RS232, RS422, or a second channel, a CM 1241 communication module (e.g., 6ES7241-1AH32-0XB0) is required. The Modbus library version must match the module; consult the Siemens Industry Online Support portal for the latest library compatibility list.
- Library installation. Install the "Modbus (RTU/ASCII) Master and Slave" library package in TIA Portal. The library ships as a global library ("Modbus_Vxx") and must be opened once and copied into the project so the function blocks appear under "Communication" → "Modbus".
- Wiring and termination. For 2-wire RS485 networks, verify that the termination resistor is enabled only at the two physical ends of the bus. On the CPU's onboard port, this is the three-position switch on the bottom of the CPU (left = terminated, center = no termination with pull-up/pull-down, right = no termination, no bias).
- Station address planning. Assign a unique Modbus station address (1–247) to the slave. Address 0 is the broadcast address and is write-only; it is not used for normal polling.
Designing the Data Block Layout
The cleanest implementation uses a single, well-named data block with four ARRAY structures, one per Modbus table. This keeps the four reference tables separate, makes the slave's access semantics explicit, and provides a single location to attach documentation and the "Writable from HMI/OPC UA" attribute described later.
DATA_BLOCK "ModbusData"
{ S7_Optimized_Access := 'FALSE' }
STRUCT
// --- Coils (read/write) ---
Coils : ARRAY[0..127] OF BOOL; // 16 bytes, FC01/05/15
// --- Discrete Inputs (read-only) ---
DiscInputs : ARRAY[0..127] OF BOOL; // 16 bytes, FC02 only
// --- Input Registers (read-only, 16-bit) ---
InRegs : ARRAY[0..63] OF WORD; // 128 bytes, FC04 only
// --- Holding Registers (read/write, 16-bit) ---
HoldRegs : ARRAY[0..127] OF WORD; // 256 bytes, FC03/06/16
// --- Pad to word boundary to make offsets predictable ---
_pad : ARRAY[0..1] OF BYTE;
END_STRUCT;
END_DATA_BLOCK
MB_DB of MB_SLAVE and the user data block must be non-optimized (S7_Optimized_Access = FALSE). The Modbus library uses absolute byte offsets in its ANY pointers and cannot resolve symbolic addresses inside an optimized block. This is one of the most common commissioning errors.With the layout above, the byte offsets passed to MB_SLAVE resolve as follows. Byte offsets are relative to the start of the data block; they are the values that must be assigned to the MB_ADDR_xxx inputs if those are used, or be the start of the array passed via ANY.
| Modbus Table | Symbol | Byte Offset (P#) | Size | Master Address Range | Master FCs |
|---|---|---|---|---|---|
| Coils | DB1.DBX0.0 | P#0.0 | 128 bits (16 bytes) | 00001 – 00128 | 01, 05, 15 |
| Discrete Inputs | DB1.DBX16.0 | P#16.0 | 128 bits (16 bytes) | 10001 – 10128 | 02 |
| Input Registers | DB1.DBW32 | P#32.0 | 64 words (128 bytes) | 30001 – 30064 | 04 |
| Holding Registers | DB1.DBW160 | P#160.0 | 128 words (256 bytes) | 40001 – 40128 | 03, 06, 16 |
The master sees four completely independent address spaces, even though all four physically live in a single DB. The MB_SLAVE instance dispatches the incoming PDU to the correct region based on function code and address range, and the PLC program reads from / writes to the appropriate substructure.
Enforcing Read-Only Behavior on the Slave
Modbus is silent on whether a slave should accept writes to a particular register; the four-table model only guarantees that Discrete Inputs and Input Registers are read-only at the protocol level. To force a subset of Holding Registers or Coils to be read-only, the application program must override any writes that arrive. Two complementary techniques are used in real deployments.
Technique 1: Application-Level Override (Cyclic Recopy)
Mirror every "read-only" holding register to a write-disabled coil or register and have the application code (in OB1 or a cyclic OB) overwrite the value in the holding register area every scan. The master sees a brief window where its value lands, but the next scan the PLC repaints it. This is the technique described in the source material as "if you cyclically overwrite holding register with your values, the master can write something to it but it will be automatically overwritten by your system." It is reliable, has no library dependencies, and works on any firmware.
Technique 2: TIA Portal DB Attribute "Writable from HMI/OPC UA"
In TIA Portal, individual tags in a data block expose a security-related attribute called Accessible from HMI/OPC UA and Writable from HMI/OPC UA. These attributes govern the visibility and write authorization through the OPC UA server of the S7-1200 (firmware 4.2 or later with an active OPC UA license). They do not gate Modbus RTU traffic; an external master using MB_SLAVE on the RS485 port has no awareness of OPC UA attributes. They are, however, useful if the same data block is also exposed via OPC UA: a tag with Writable from HMI/OPC UA = unchecked becomes read-only for OPC UA clients while remaining writable for the Modbus master.
To set the attribute:
- Open the data block "ModbusData".
- Click a tag such as
HoldRegs[0]. - In the inspector pane under "Properties" → "Attributes", uncheck Accessible from HMI/OPC UA if the tag should not be exposed to OPC UA at all, or uncheck Writable from HMI/OPC UA if it should be visible but read-only.
- Repeat for each tag that requires restricted access.
This approach does not, by itself, prevent a Modbus master from writing the same tag. Combine it with the application-level override in OB1 if Modbus-side write protection is the requirement.
Configuring Each Modbus Table on MB_SLAVE
The next subsections describe the four tables, the wiring inside the data block, and the OB logic that maintains the read-only contract.
2.1 Coils (Read/Write, FC 01/05/15)
Use Coils for boolean setpoints and commands the master should be allowed to switch — remote enable signals, set/reset bits, mode selectors, alarm acknowledgements. Because Modbus coils are 1-bit and the S7-1200 ARRAY OF BOOL packs 8 coils per byte, the master's view of Coils[0] corresponds to bit 0 of the byte at the array's base address.
If a particular coil must be read-only from the master, the application logic in OB1 should ignore the corresponding bit and overwrite it with the controlled value on every scan. The PLC still exposes the slot to the master at the same address; the master can technically issue FC 05 or 15 against it, but the value is overwritten before any application code sees it.
2.2 Discrete Inputs (Read-Only, FC 02)
Discrete Inputs are the read-only bit table. Use them for status bits, feedback signals, and interlocks that the master should see but not change. The S7-1200 implementation accepts FC 02 against the configured input coil area; any FC 05/15 against the same address range returns exception 02.
Typical application logic refreshes the discrete input area from physical inputs in OB1 or a process image update OB:
// OB1 fragment — refresh read-only bit table from process image
"ModbusData".DiscInputs[0] := "I0.0"; // Field input 0 → DI 10001
"ModbusData".DiscInputs[1] := "I0.1"; // Field input 1 → DI 10002
"ModbusData".DiscInputs[2] := "DB_Motor".Running; // Internal status → DI 10003
2.3 Input Registers (Read-Only, FC 04)
Input Registers are 16-bit words and are the read-only analog table. Use them for measured values, scaled sensor data, and internal diagnostic counters. The application populates the input register area on every scan; the master can only read.
Because the Modbus data model is fixed at 16-bit words, sending a 32-bit floating point across Modbus requires engineering the byte order. The two prevailing conventions are:
- Word-swapped (Modicon convention). The lower-addressed word holds the upper half of the float. This is the default of most Modbus masters (Modbus Poll, ModScan, Schneider M340 CPU). Each float consumes two consecutive input registers (e.g., IR[0]–IR[1] for the first float).
- Byte-swapped (little-endian register order). The lower-addressed word holds the lower half of the float. Common on some Allen-Bradley masters and on field devices that have a "Modbus Float Order" setting.
On the S7-1200, the canonical code to write a REAL into the input register area looks like this:
// Load a REAL into IR[0..1] using word-swap (Modicon) order
VAR_TEMP
dw : DWORD;
wHi, wLo : WORD;
END_VAR
dw := REAL_TO_DWORD("Process".Temperature_C); // 32-bit IEEE-754
wLo := DWORD_LOW_WORD(dw); // low half
wHi := DWORD_HIGH_WORD(dw); // high half
"ModbusData".InRegs[0] := wLo; // low word first (Modicon)
"ModbusData".InRegs[1] := wHi; // high word second
2.4 Holding Registers (Read/Write, FC 03/06/16)
Holding Registers are the read/write 16-bit word table. They are the most common destination for setpoints, recipe data, and operator-entered values. The master reads and writes with FC 03/06/16.
For values that must be read-only from the master but consumed by the application, the canonical pattern is:
- The application copies its authoritative value into
HoldRegs[N]on every scan of OB1. - Any write from the master (FC 06 or 16) lands in
HoldRegs[N]at the end of the master's transaction. - On the next scan, the application overwrites
HoldRegs[N]with the authoritative value, masking the master's write.
This pattern is robust but introduces a one-scan window of vulnerability. For tighter guarantees, gate the master write at the application level with a comparison: if the master wrote, set a sticky fault bit and refuse to apply the setpoint until operator acknowledgement.
Verification and Commissioning
After downloading the project, perform the following checks with a Modbus master on a bench network before the unit is shipped to the field.
-
Loopback read of Holding Registers. From the master, issue FC 03 against register 40001 (PDU 0x0000). Confirm that the value matches the initial value in
HoldRegs[0]. - Read-only verification of Input Registers. Issue FC 04 against 30001. Confirm that the value reflects the application-set input. Then issue FC 06 against the same address. The slave must respond with exception 02 (Illegal Data Address), and the value must remain unchanged.
- Read-only verification of Discrete Inputs. Issue FC 02 against 10001. Issue FC 05 against the same address. Confirm exception 02.
- Write test on Holding Registers. Issue FC 06 against 40001 with a known value. Read back with FC 03 and confirm.
- Coil write test. Issue FC 05 against 00001 with value 0xFF00. Read back with FC 01. Confirm the bit transitioned.
- Application-level override test. For any holding register designated as "read-only from master" via application logic: from the master, write a test value. Confirm that on the next scan the value is reverted to the application-controlled value.
-
Status word inspection. With the program running, monitor the
STATUSoutput ofMB_SLAVEfor non-zero values. The library defines the status codes in the instruction's help text; 0x0000 is "no error" and nonzero values indicate a malformed PDU, parity error, or address out of range.
Troubleshooting Matrix
| Symptom | Likely Cause | Resolution |
|---|---|---|
| Master gets no response on any function code | Baud rate / parity mismatch, A-B wiring reversed, termination missing on long cable, station address mismatch | Verify BAUD, PARITY, and MB_ADDR on both sides; check that the RS485 A/B (or D+/D- on some vendors) are not crossed; confirm 120 Ω termination is enabled only at the two ends |
| FC 03 returns 0xFFFF for every register | Modbus station address set to 0 (broadcast) or address does not match the master poll | Confirm MB_ADDR is in 1–247; check master poll station address |
| Exception 02 (Illegal Data Address) on FC 03 against 40050 but works against 40001 | Address is outside the configured holding register area | Increase the size of the HoldRegs array or update the master to poll only within the supported range |
| STATUS = 0x8180 / 0x8181 after a write | Modbus library diagnostic code: parity or framing error on the wire | Inspect the cable for noise; reduce baud rate; verify shield grounding |
| Master can read but write seems to "stick" for one scan then revert | Application-level override active on that register — expected behavior | Confirm with the application engineer whether the override is intentional; if not, remove the OB1 overwrite for that tag |
| OPC UA client can write a tag that the Modbus master cannot | The "Writable from HMI/OPC UA" attribute is checked; Modbus access is governed separately | Uncheck "Writable from HMI/OPC UA" on the tag if OPC UA read-only is also required; remember that this does not affect Modbus |
| MB_SLAVE reports 0x80A1 / 0x80A2 (DB type) | Configured data DB is optimized (S7_Optimized_Access = TRUE) | Set the user data DB and the MB_SLAVE instance DB to non-optimized access |
Reference — Modbus Address Map to Publish to the Master Integrator
Hand the following table to the master integrator. It defines the contract between the S7-1200 slave and the polling master; any change to the data block layout must be reflected here.
| Modbus Reference | PDU Address | Symbolic Tag (TIA) | Data Type | Access | Function Codes | Engineering Unit / Meaning |
|---|---|---|---|---|---|---|
| 00001 | 0x0000 | ModbusData.Coils[0] | BOOL | R/W | 01, 05, 15 | Remote Enable |
| 00002 | 0x0001 | ModbusData.Coils[1] | BOOL | R/W | 01, 05, 15 | Reset Command |
| 10001 | 0x0000 | ModbusData.DiscInputs[0] | BOOL | R | 02 | Pump 1 Running |
| 10002 | 0x0001 | ModbusData.DiscInputs[1] | BOOL | R | 02 | High Pressure |
| 30001–30002 | 0x0000–0x0001 | ModbusData.InRegs[0..1] | FLOAT (Modicon order) | R | 04 | Process Temperature, °C |
| 30003 | 0x0002 | ModbusData.InRegs[2] | INT (scaled x10) | R | 04 | Process Pressure, 0.1 bar/bit |
| 40001 | 0x0000 | ModbusData.HoldRegs[0] | INT | R/W | 03, 06, 16 | Setpoint: Flow Rate, 0.1 L/min/bit |
| 40002 | 0x0001 | ModbusData.HoldRegs[1] | INT | R/W | 03, 06, 16 | Setpoint: Temperature, °C |
| 40050–40051 | 0x0032–0x0033 | ModbusData.HoldRegs[49..50] | FLOAT (Modicon order) | R/W | 03, 06, 16 | Recipe: Line Speed, m/s |
What is the proper way to designate a Modbus register as read-only on the S7-1200?
There is no per-tag read-only flag in the Modbus protocol itself. The standard mechanism is to map the value into the Input Register table (FC 04) for 16-bit read-only data or the Discrete Input table (FC 02) for 1-bit read-only data. Any write attempt (FC 06/16 or FC 05/15) against those addresses returns Modbus exception 02 (Illegal Data Address). For read-only values that must live in the Holding Register table for compatibility with the master, implement an OB1 application-level override that recycles the value every scan.
Does the "Writable from HMI/OPC UA" attribute block Modbus writes?
No. The attribute gates the OPC UA server of the S7-1200 only. A Modbus RTU master connected to the same CPU via RS485 has no awareness of OPC UA attributes. Use the cyclic OB override pattern in addition to (or instead of) the attribute if Modbus write protection is the requirement.
Why does my S7-1200 return exception 02 for an address I configured in the data block?
Exception 02 (Illegal Data Address) is returned when the requested address falls outside the configured MB_ADDR range. The data block may contain the variable, but if the MB_SLAVE pointer is not configured to include that byte offset, the slave treats the address as out of range. Verify the start offset and length of each table in the MB_SLAVE call and ensure the symbolic addresses resolve inside the configured region.
How many Holding Registers can the S7-1200 Modbus RTU slave expose?
The limit is bounded by the data block size and the S7-1200 work memory, not by a hard library limit. Practical deployments use 200–500 words in the Holding Register area without issue. Holding Register polling with FC 03 supports 1–125 words per request, so partition the map into chunks of 125 or fewer to minimize transaction overhead.
What is the correct byte order for sending a REAL via Modbus?
The Modbus standard does not specify byte order. Two conventions exist: Modicon word-swapped (low word first, high word second) and little-endian (high word first, low word second). Confirm with the master integrator which order is expected. On the S7-1200, use DWORD_HIGH_WORD and DWORD_LOW_WORD from the IEC standard functions to extract the two words from a REAL cast to DWORD, then place them in the appropriate ARRAY OF WORD positions.