Overview
Modbus RTU is one of the most common industrial serial protocols, and the Siemens S7-1200 family supports it natively through the MB_COMM_LOAD and MB_SLAVE function blocks when paired with a CM 1241 RS-485 or CM 1241 RS-232 communication module. This article walks through a field-proven configuration sequence and solves the three issues engineers most frequently hit when first commissioning the link: configuring the slave (Modbus) address, getting MB_HOLD_REG accepted by the TIA Portal, and recovering from a silent bus where NDR and DR stay at 0 with no error code.
Modbus itself is a master/slave request/reply protocol defined by Modbus Organization and is widely used for simple register-based polling between PLCs, VFDs, energy meters, and SCADA tools such as CAS Modbus Scanner, Modbus Poll, and Modbus Tools. The serial RTU variant uses RS-485 (or RS-232) with 8 data bits, even parity, and 1 stop bit as the default framing. Background reading is available at Wikipedia – Modbus and the Schneider Electric primer at What is Modbus and How does it work?.
Prerequisites
Before configuring the blocks, confirm the following hardware and software are in place.
| Item | Requirement |
|---|---|
| CPU | S7-1200 (any firmware; MB_SLAVE instructions require firmware ≥ V2.0 on the CM, with current TIA Portal libraries supporting V4.x and later) |
| Communication module | 6ES7241-1CH30-1XB0 (CM 1241 RS-485) or 6ES7241-1AH30-1XB0 (CM 1241 RS-232) |
| Engineering software | SIMATIC STEP 7 (TIA Portal) V13 SP1 or later; project example provided here works on V13 SP1 as well as later Service Packs |
| Firmware | CM 1241 firmware ≥ V1.0 (modules ship with current firmware from factory) |
| Master test tool | CAS Modbus Scanner, Modbus Poll, or any Modbus/RTU master capable of 19200 / 8E1 (CAS Modbus Test was used in the field report) |
| Wiring | 2-wire RS-485 bus, 120 Ω termination at both ends, common ground between devices |
Hardware Wiring and Module Insertion
Insert the CM 1241 RS-485 to the left of the CPU. The module is detected automatically by TIA Portal and appears in Device view. Configure the module's serial port parameters from the Properties > RS-485 interface tab to match the master you intend to connect to:
| Parameter | Typical Value | Notes |
|---|---|---|
| Baud rate | 19200 bit/s | Common default; 9600 is also widely used |
| Parity | Even | Modbus RTU standard is 8E1 (even parity, 1 stop bit) |
| Data bits | 8 | Fixed by Modbus RTU |
| Stop bits | 1 | Fixed by Modbus RTU |
| Flow control | None | RS-485 does not use hardware flow control |
| Termination | Enabled on end nodes only | DIP switch on back of CM 1241, or external 120 Ω resistor |
Slave Address: Where to Set It
The Modbus slave address is not derived from the CPU's IP address. Setting a unit to "20" because its Ethernet IP ends in ...20 is a common mistake. The slave address is a property of the Modbus link and is configured on the MB_SLAVE block, parameter SLAVE_ADDR.
Valid range is 1 to 247. Address 0 is the Modbus broadcast address and should not be used for normal polling. The address you set must match what the master (e.g. CAS Modbus Test) is configured to poll.
192.168.0.20) is meaningful only for Modbus/TCP. For Modbus/RTU on RS-485, every device on the bus must have a unique number from 1 to 247 regardless of any IP address the device might also have.
MB_COMM_LOAD — Port Parameterization
The MB_COMM_LOAD block parameterizes the CM 1241's serial port. It must be executed once after the PLC starts, typically on the first scan or behind a positive edge from a system bit. Calling it every scan wastes CPU time and is not required.
| Input | Type | Meaning |
|---|---|---|
| REQ | BOOL | Trigger pulse; rising edge starts parameterization |
| PORT | PORT (HW identifier) | HW ID of the CM 1241 from device configuration |
| BAUD | DINT | Baud rate, e.g. 9600 or 19200
|
| PARITY | DINT | 0 = None, 1 = Odd, 2 = Even |
| FLOW_CTRL | DINT | 0 = None (use this for RS-485) |
| RTS_ON_DLY | DINT | RTS on delay in ms; 0 typical for RS-485 |
| RTS_OFF_DLY | DINT | RTS off delay in ms; 0 typical for RS-485 |
| RESP_TO | DINT | Response timeout in ms (e.g. 1000) |
Drive REQ from a one-shot that triggers only on the first scan cycle:
// OB1 - First-scan trigger
// In a global DB or as a tag:
#FirstScanDone : BOOL;
// Ladder snippet:
// [FirstScan] --[NOT #FirstScanDone]--( #MBCommLoad.REQ )
// [MBCommLoad.DONE]--( #FirstScanDone )
If MB_COMM_LOAD reports STATUS = 16#8180 the port is in use or the HW ID is wrong; 16#8181 indicates a parity/baud combination not supported.
MB_SLAVE — Modbus Server Configuration
Place an MB_SLAVE instance in OB1 and call it on every scan once MB_COMM_LOAD.DONE is true. The block processes Modbus requests and exposes the S7-1200's holding registers to a Modbus master.
| Input | Type | Meaning |
|---|---|---|
| MB_ADDR | UINT | Modbus station address (1–247). This is your slave ID. |
| MB_HOLD_REG | VARIANT (POINTER) | Pointer to the start of the holding register area; see next section |
| NDR | BOOL | New data received from master (one-scan pulse) |
| DR | BOOL | Data read by master (one-scan pulse) |
| ERROR | BOOL | Error flag (one-scan pulse — capture it!) |
| STATUS | WORD | Detailed error/status code |
ERROR and STATUS outputs are valid for only one scan cycle. If you do not latch them into a static tag (e.g. with an M bit or DB word), you will see no error information when you go online.
A standard MB_SLAVE supports the following Modbus function codes; non-supported requests return exception code 01 (illegal function).
| FC | Function | Address range |
|---|---|---|
| 01 | Read Coils | 0xxxx (process image output, Q area) |
| 02 | Read Discrete Inputs | 1xxxx (process image input, I area) |
| 03 | Read Holding Registers | 4xxxx (MB_HOLD_REG area) |
| 04 | Read Input Registers | 3xxxx (input word area / IW) |
| 05 | Write Single Coil | 0xxxx |
| 06 | Write Single Register | 4xxxx |
| 15 | Write Multiple Coils | 0xxxx |
| 16 | Write Multiple Registers | 4xxxx |
| 23 | Read/Write Multiple Registers | 4xxxx |
Configuring MB_HOLD_REG — The Most Common Pitfall
The MB_HOLD_REG input is a VARIANT pointer to the start of a contiguous block of words that the master will read/write with FC 03, FC 06, FC 16, and FC 23. Two pointer formats are accepted:
Format A — Optimized Data Block (Recommended)
Create a global DB whose data block number is non-optimized (in DB properties, uncheck "Optimized block access"). Within it, declare either a structured type or a simple word array.
// Data block HR_DB, non-optimized
DATA_BLOCK "HR_DB"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
NON_RETAIN
STRUCT
Data : STRUCT
Temp_1 : REAL; // 4 words -> Modbus 40001..40004
Temp_2 : REAL; // 4 words -> Modbus 40005..40008
Avg_Time : UDINT; // 4 words -> Modbus 40009..40012
Good_Count: UDINT; // 4 words -> Modbus 40013..40016
Bad_Count : UDINT; // 4 words -> Modbus 40017..40020
END_STRUCT;
END_STRUCT;
END_DATA_BLOCK
In the MB_HOLD_REG input on the MB_SLAVE block, type the symbolic name of the variable. Because the DB is non-optimized, TIA Portal will accept a simple symbol such as "HR_DB".Data or even "HR_DB" at the DB boundary.
"HR_DB".Data. with a trailing dot, manually delete the extra period. Leaving it in place produces "Incorrect pointer on MB_HOLD_REG" (status 16#8382).
Format B — Absolute M-Area Pointer
If you do not want to use a DB, you can point the holding register area directly into the M (merker) area using an Any-pointer literal in the form P#Mn.0 WORD x.
// 100 words of holding registers starting at M50.0
P#M50.0 Word 100
This reserves Modbus addresses 40001 to 40100 mapped onto MW50 through MW250 (2 bytes per word). Watch the size — a 100-word block consumes 200 bytes of M memory.
Common Status Codes for MB_HOLD_REG Errors
| STATUS (hex) | Meaning | Fix |
|---|---|---|
| 16#8183 | Pointer to non-existent DB / wrong symbolic name | Verify the DB number exists and the name is correct |
| 16#8382 | Pointer length/format invalid ("Incorrect pointer on MB_HOLD_REG") | Remove trailing period in the symbol; ensure DB is non-optimized; re-check Any-pointer syntax |
| 16#8383 | Data type mismatch (e.g. trying to map BOOLs into a word region) | Use INT, WORD, DWORD, REAL, or UDINT elements only |
| 16#8384 | MB_HOLD_REG overlaps with system memory or is outside the data area | Re-locate the DB to user range; check the address does not exceed 60 KB |
Step-by-Step Commissioning Procedure
- Insert CM 1241 RS-485 in the device configuration. Note its Hardware identifier (HW ID) — typically 269 or similar.
-
Create global DB
HR_DBwith optimized access disabled. Add a structure of REAL and UDINT variables as shown above. -
Open OB1 and add an
MB_COMM_LOADinstance. Connect a one-shot pulse toREQ, the CM 1241 HW ID toPORT, and constants for baud/parity/flow control. -
Add an
MB_SLAVEinstance in OB1. SetMB_ADDRto the desired station address (1–247). -
Wire
MB_HOLD_REGto theDatastructure ofHR_DB. Confirm there is no trailing period in the symbol. -
Capture
ERRORandSTATUSinto static tags. Without this, you cannot see what went wrong after the block returns. -
Download the project, go online, and force
MB_COMM_LOAD.REQfor one scan if it has not already executed. -
Connect the master tool (CAS Modbus Scanner) to the RS-485 port, set the same baud/parity, and poll FC 03 at register 0. The response should show the current values of
HR_DB.Data.Temp_1,Temp_2, etc.
Verifying the Link
Watch MB_SLAVE.NDR (new data — write request received) and MB_SLAVE.DR (data read) on each scan. The master should generate a steady stream of pulses on these bits at the master's poll rate. If both remain 0 and no error is reported, the bus is silent — see the troubleshooting matrix below.
| Symptom | Likely Cause | Action |
|---|---|---|
ERROR pulses once with STATUS = 16#8382
|
Bad MB_HOLD_REG pointer | Remove trailing period, verify DB is non-optimized |
ERROR with STATUS = 16#8180
|
MB_COMM_LOAD did not run, or HW ID wrong | Check PORT input, force one-shot |
| No error, NDR = DR = 0, master times out | Wiring swap (A/B inverted), missing termination, or mismatched baud | Swap A+/B− lines; check 120 Ω at both ends; verify baud/parity match master |
| Master receives exception code 02 (illegal data address) | Polling address outside the MB_HOLD_REG range | Ensure request stays inside the 100-word region; remap the DB |
| Master receives exception code 03 (illegal data value) | Quantity too high for the FC | Limit per-request word count to the FC specification (e.g. 125 words for FC 03) |
| Communication works briefly then drops | RS-485 echo / biasing missing on long cables | Add fail-safe bias resistors (typically 680 Ω to +5 V on A, 680 Ω to GND on B) |
Capturing the One-Scan Error
Because the ERROR bit is high for a single scan, capture it into a retentive tag for offline inspection. A minimal pattern looks like this:
// M-memory capture registers
// %MW100 : MB_SLAVE.ERROR (latched)
// %MW102 : MB_SLAVE.STATUS (latched)
// Ladder:
// [MB_SLAVE.ERROR] --( #CaptureErrorSet ) // SET bit
// [MB_SLAVE.ERROR] --( MOVE MB_SLAVE.STATUS > %MW102 )
// [MB_SLAVE.NDR] --( MOVE MB_SLAVE.STATUS > %MW104 )
With this in place, if the master polls an unsupported function, you will see the latched status at %MW102 on the next online session and can decode it against the table above or the Siemens SIMATIC Technical Support knowledge base.
Differentiating Modbus/RTU from Modbus/TCP
A frequent misconfiguration is to leave the master's TCP/IP parameters in place while wiring a serial port. If the tool expects 192.168.0.20:502, you will not see any response on RS-485. Confirm the master is in RTU mode and pointed at the correct COM port.
| Property | Modbus/RTU | Modbus/TCP |
|---|---|---|
| Physical layer | RS-232 or RS-485 | Ethernet |
| Frame format | Binary with CRC-16 | MBAP header + PDU |
| S7-1200 instruction set | MB_COMM_LOAD + MB_SLAVE / MB_MASTER | TCON, TSEND, TRCV with FB "MB_CLIENT" / "MB_SERVER" from the Modbus/TCP library |
| Address concept | Slave ID 1–247 on a shared bus | IP address + port 502 per device |
| Typical errors if swapped | Master times out | Master returns connection refused |
Best Practices and Field Notes
- Always use a non-optimized DB for the holding register area. Optimized DBs are not accepted by the legacy MB_SLAVE block in classic STEP 7 libraries.
- Use a single positive-edge trigger to call
MB_COMM_LOAD. Continuous calls do not harm the CPU but waste cycle time. - Latch
ERRORandSTATUSoutputs into retentive memory. Without this, you will not be able to diagnose intermittent issues after the fact. - Place a 120 Ω termination at each physical end of the RS-485 bus, never in the middle. The CM 1241 has an internal switch you can use if it is the last device.
- Match A+ to A+ and B− to B−. On Siemens terminals these are labeled
T/R+andT/R−. If the master times out with no errors, swap them. - For long cables (> 10 m) or noisy environments, add failsafe bias (typically 680 Ω pull-up to +5 V on T/R+ and 680 Ω pull-down to GND on T/R−).
- For more than 31 devices or cables longer than 1200 m, install an RS-485 repeater (e.g. Phoenix Contact PSI-REP-RS485W2 or similar).
- Verify Modbus standard framing: 8 data bits, even parity, 1 stop bit (8E1) unless the device explicitly requires something else.
Project Archive Compatibility
The example project discussed in the source material was originally provided in TIA Portal V13 SP2 format. To open it on V13 SP1, either upgrade the installation to SP2 or later, or export the program blocks manually and re-import them into a fresh SP1 project. The MB_SLAVE and MB_COMM_LOAD blocks themselves are forward-compatible — the differences are at the project container level, not in the block logic.
Quick-Reference: Required Block Wiring
// OB1 main scan — call both blocks
// MB_COMM_LOAD (called once on first scan)
// REQ := #FirstScanEdge
// PORT := 269 // HW ID of CM 1241
// BAUD := 19200
// PARITY := 2 // 2 = Even
// FLOW_CTRL := 0
// RTS_ON_DLY := 0
// RTS_OFF_DLY:= 0
// RESP_TO := 1000
// DONE -> %M10.0
// ERROR -> %M10.1
// STATUS -> %MW12
// MB_SLAVE (called every scan after MB_COMM_LOAD.DONE = 1)
// MB_ADDR := 1 // Modbus slave ID
// MB_HOLD_REG:= "HR_DB".Data // No trailing period!
// NDR -> %M20.0 // Latch to inspect
// DR -> %M20.1
// ERROR -> %M20.2 // Latch with S/R flip-flop
// STATUS -> %MW22
FAQ
Why does the S7-1200 MB_SLAVE report "Incorrect pointer on MB_HOLD_REG" even though the DB exists?
The most common cause is a trailing period in the symbolic name (for example "HR_DB".Data. instead of "HR_DB".Data). The TIA Portal input helper often appends one when you select the symbol. Delete the extra period. The second most common cause is an optimized-access DB — MB_SLAVE requires the DB to have optimized access disabled.
Can I use the CPU's IP address (e.g. 192.168.0.20) as the Modbus slave address?
No. Modbus/RTU uses a station ID between 1 and 247 on a shared RS-485 bus, and the address is set on the MB_ADDR input of the MB_SLAVE block. The CPU's IP address is meaningful only for Modbus/TCP, which uses different instructions (MB_CLIENT/MB_SERVER over TCON).
How do I make MB_SLAVE work with a non-DB memory area such as the M (merker) area?
Enter an Any-pointer literal in the form P#Mn.0 WORD x on the MB_HOLD_REG input, where n is the starting byte and x is the word count. For example, P#M50.0 WORD 100 reserves 100 words (200 bytes) starting at MB50 for Modbus FC 03/06/16/23 access.
Why are NDR and DR both 0 with no error reported?
The MB_SLAVE block has nothing to do unless a Modbus master actually transmits on the bus. Verify (1) the master is in RTU mode on the correct COM port, (2) baud and parity match (8E1 is standard), (3) A+/B− wiring is not swapped, and (4) the bus is terminated with 120 Ω at both ends. Also confirm MB_COMM_LOAD has run successfully (DONE = 1) before MB_SLAVE starts processing.
How do I see the MB_SLAVE error code if it only pulses for one scan?
Latch the ERROR bit and the STATUS word into retentive M-memory or DB tags every scan. Without this latching logic, the error is lost the moment the next scan starts and online diagnostics show nothing. The same technique applies to NDR and DR if you need a record of which Modbus request was last serviced.