S7-1200 Modbus FC23 Mastervolt Charger: Read/Write Workaround
The Siemens S7-1200 PLC cannot issue Modbus function code 23 (Read/Write Multiple Registers, 0x17) natively, yet Mastervolt battery chargers expose a significant portion of their telemetry, configuration, and control registers through this combined transaction over a MasterBus-to-Modbus converter. This reference documents the underlying problem, the converter topology, the FC23 payload that Mastervolt produces, and three field-proven workarounds that keep the project on a S7-1200 CPU without changing the PLC family.
1. Problem Overview
A standard engineering scenario pairs a Siemens SIMATIC S7-1200 CPU 1214C DC/DC/DC with a CM 1241 RS422/RS485 communications module (Siemens part number 6ES7241-1CH32-0XB0) and reads/writes several Mastervolt Mass Combi, ChargeMaster, or CombiMaster battery chargers. The chargers do not expose a raw Modbus RTU interface; they speak MasterBus, a CAN-based proprietary bus. A Mastervolt MasterBus-Modbus converter translates MasterBus frames into Modbus RTU registers and back again.
The complication is that the Mastervolt side of this converter implements several combined read/write services using Modbus function code 23 (0x17) with custom IDB/IDAL addressing tags. The S7-1200 Modbus RTU library (the MB_COMM_LOAD and MB_MASTER instructions documented in the TIA Portal help) only supports the following function codes in the standard instruction set:
| FC | Name | S7-1200 MB_MASTER |
|---|---|---|
| 01 (0x01) | Read Coils | Supported |
| 02 (0x02) | Read Discrete Inputs | Supported |
| 03 (0x03) | Read Holding Registers | Supported |
| 04 (0x04) | Read Input Registers | Supported |
| 05 (0x05) | Write Single Coil | Supported |
| 06 (0x06) | Write Single Register | Supported |
| 15 (0x0F) | Write Multiple Coils | Supported |
| 16 (0x10) | Write Multiple Registers | Supported |
| 23 (0x17) | Read/Write Multiple Registers | Not supported |
FC23 is therefore the obstacle. It is not a "missing feature" in the sense of a bug: the S7-1200 Modbus library was designed against the classic Modbus RTU application layer and FC23 is an optional extension defined in Modbus RFC extension. Any project that requires it must either use a workaround on the S7-1200 or insert a translation stage in front of the PLC.
STATUS = 0x80C8 (illegal function) on the wire.2. Hardware Topology
The complete bus layout is shown below. Each component, its order number, and its role is called out so the same setup can be replicated in cabinet design.
| Position | Component | Order / Part Number | Function |
|---|---|---|---|
| A | S7-1200 CPU 1214C DC/DC/DC | 6ES7214-1AG40-0XB0 | PLC, executes Modbus master |
| B | CM 1241 RS422/RS485 | 6ES7241-1CH32-0XB0 | Serial port, RS485 2-wire to converter |
| C | Mastervolt MasterBus-Modbus converter | 77030800 (Mastervolt) | Translates MasterBus <-> Modbus RTU |
| D1..Dn | Mastervolt chargers (Mass Combi, ChargeMaster, CombiMaster) | Various (e.g. ChargeMaster 12/35-3, 44010350) | Slaves on MasterBus CAN, exposed as Modbus registers |
| E | MasterBus terminator | Mastervolt 77031500 | 120 Ω CAN terminator, both ends of bus |
2.1 RS485 Cabling Rules
- Use a twisted pair, characteristic impedance 120 Ω, e.g. Belden 3106A or Lapp ETHERLINE 2-pair.
- Place the cable shield on the converter side only, bonded to PE through a 10 nF / 1 MΩ parallel network to bleed RF without passing 50/60 Hz fault current.
- Maximum bus length at 19200 bit/s: 1200 m. Mastervolt converters ship with a fixed baud default of 19200 8N1 when ordered for Modbus RTU operation.
- Connect pin 8 (T/R+, A) on the CM 1241 to A on the converter; pin 3/4 (T/R-, B) to B. The CM 1241 has no internal termination; insert a 120 Ω resistor across A/B at each physical end of the RS485 segment.
3. The MasterBus / Modbus Converter
MasterBus is a CAN 2.0B based proprietary bus running at 250 kbit/s with 11-bit identifiers. Each MasterBus device has a unique Device ID (1..247) that becomes its Modbus slave address once the Mastervolt converter has been configured. The converter acts as a transparent bilingual gateway: every MasterBus message produced by a charger (state, alarms, configuration, telemetry) is mirrored into a set of Modbus holding registers on the converter's RTU side, and writes arriving over Modbus are wrapped back into MasterBus messages and forwarded to the addressed device.
Mastervolt distributes an open register map for each device family. For example, the ChargeMaster 12/35-3 exposes the following illustrative block (verify against the current datasheet for the specific charger you are using):
| Modbus Register (0-based) | IDB Tag | Access | Meaning | Type |
|---|---|---|---|---|
| 0x0001 | IDB.ChargeState | R | 0=Off, 1=Bulk, 2=Absorption, 3=Float, 4=Storage | UINT16 |
| 0x0002 | IDB.BatteryVoltage | R | Pack V × 100 | UINT16 |
| 0x0003 | IDB.ChargeCurrent | R | Pack A × 10 | UINT16 |
| 0x0004 | IDB.TempSensor | R | °C + 40 offset | INT16 |
| 0x0010 | IDB.MaxChargeCurrent | R/W | A × 10 | UINT16 |
| 0x0011 | IDB.ChargeMode | R/W | 0=Off, 1=On, 2=Auto | UINT16 |
| 0x0012 | IDAL.SetBulkVoltage | R/W | V × 100 | UINT16 |
| 0x0013 | IDAL.SetFloatVoltage | R/W | V × 100 | UINT16 |
The IDAL tags are the configuration block: they must be read, modified, and written back. On a full MasterBus session the converter combines the read of a status block and the write of a configuration block into a single FC23 transaction to keep the configuration atomic. That is the function code the S7-1200 cannot issue.
4. Decoding FC23 (Read/Write Multiple Registers, 0x17)
The wire frame for FC23 on Modbus RTU looks like this:
| Byte | Field | Notes |
|---|---|---|
| 0 | Slave address (1..247) | MasterBus device ID |
| 1 | Function code 0x17 | Read/Write Multiple Registers |
| 2..3 | Read starting address | High / Low |
| 4..5 | Quantity of read registers (N) | 0x0001..0x007D |
| 6..7 | Write starting address | High / Low |
| 8..9 | Quantity of write registers (M) | 0x0001..0x007D |
| 10..11 | Write byte count (2×M) | |
| 12..(12+2M-1) | Write register values | High / Low per register |
| .. | CRC16 (low, high) | Modbus RTU CRC, polynomial 0xA001 |
The response returns 2 × N bytes of read register data followed by CRC. The atomicity property (one transaction, one MasterBus round-trip) is what makes FC23 attractive to the Mastervolt gateway: writing a new absorption voltage and reading back the state machine flags in the same frame keeps the converter's internal state consistent.
5. Why the S7-1200 Cannot Issue FC23
The TIA Portal V21 Modbus_Master instruction documentation lists only the function codes 01, 02, 03, 04, 05, 06, 15, and 16. Internally the instruction is implemented as a function block in the Modbus library that issues the request via the protocol stack on the CM/CP. The MODE input is a BYTE value validated against this set; any other value, including 23, is rejected before the request is encoded.
Two practical consequences follow:
- No firmware or library option on the CM 1241 can be enabled to add FC23. Newer CPUs (S7-1500 and S7-1200 G2) ship with the same library scope and likewise do not add FC23 in standard firmware.
- You cannot free-program a custom FC23 frame from the S7-1200 using the standard blocks. Free serial protocols (USS, ASCII, 3964R) do not give you arbitrary Modbus PDU encoding: they require you to build the request and decode the reply byte-by-byte, and the CM 1241's
SEND_P2P/RECV_P2Pinstructions also enforce inter-character and inter-frame timing that complicates Modbus CRC handling.
6. Workaround Architecture: Split the FC23
The lowest-risk fix is to decompose every FC23 transaction into a FC06 (or FC16) write followed by a FC03 read. The Mastervolt converter accepts these as independent operations. The only cost is the loss of the "one atomic transaction" property: between the write and the read, a second master on the same bus, or a charger event, could change the value. For SCADA monitoring with a single master on a slow (1-2 s) poll, this is acceptable.
6.1 Logical Flow
The PLC code follows this state machine on every periodic OB (e.g. OB1 cyclic, 100 ms):
- Initialize the RS485 port once in OB100 (cold restart) using
MB_COMM_LOADwith mode = RTU, baud = 19200, parity = 8N1, flow control = none. - Cycle through chargers in a ring counter. For each charger, alternate between a write phase and a read phase.
- Write phase: issue
MB_MASTERwith MODE = 6 (Write Single Register) or MODE = 16 (Write Multiple Registers), starting at the IDAL address, with the new configuration values from a data block. - Read phase: issue
MB_MASTERwith MODE = 3 (Read Holding Registers), starting at the IDB address, into a buffer data block. - Insert a configurable inter-transaction delay (default 50 ms) to let the converter complete the MasterBus write before the read pulls fresh data.
- Repeat on the next OB1 cycle until all chargers have been serviced, then loop back.
6.2 TIA Portal SCL Code Skeleton
Use two instance DBs from the Modbus library: one for MB_COMM_LOAD, one for MB_MASTER. The cyclic control is a small FB that owns the state machine.
// FB_MastervoltPoll - cyclic Modbus RTU driver
// Inputs: i_b_SlaveAddr (1..247)
// i_i_StartAddrWrite, i_i_StartAddrRead
// i_i_QuantityWrite, i_i_QuantityRead
// i_w_Payload[1..32] // values to write
// Outputs: o_w_Result[1..32] // values read
// o_b_Done, o_b_Error, o_w_Status
CASE #e_State OF
0: // IDLE
#e_State := 10;
10: // issue WRITE
#MB_MASTER_Instance.MODE := 16; // FC16 = Write Multiple
#MB_MASTER_Instance.DATA_ADDR := 400001 + #i_i_StartAddrWrite;
#MB_MASTER_Instance.DATA_LEN := #i_i_QuantityWrite;
#MB_MASTER_Instance.DATA_PTR := #Payload_DB;
#MB_MASTER_Instance.REQ := TRUE;
IF #MB_MASTER_Instance.DONE THEN
#MB_MASTER_Instance.REQ := FALSE;
#e_State := 20;
#t_Delay(IN := FALSE);
ELSIF #MB_MASTER_Instance.ERROR THEN
#o_w_Status := #MB_MASTER_Instance.STATUS;
#o_b_Error := TRUE;
#e_State := 0;
END_IF;
20: // inter-transaction delay (50 ms)
#t_Delay(IN := TRUE, PT := T#50ms);
IF #t_Delay.Q THEN
#e_State := 30;
END_IF;
30: // issue READ
#MB_MASTER_Instance.MODE := 3; // FC03 = Read Holding
#MB_MASTER_Instance.DATA_ADDR := 400001 + #i_i_StartAddrRead;
#MB_MASTER_Instance.DATA_LEN := #i_i_QuantityRead;
#MB_MASTER_Instance.DATA_PTR := #Result_DB;
#MB_MASTER_Instance.REQ := TRUE;
IF #MB_MASTER_Instance.DONE THEN
#MB_MASTER_Instance.REQ := FALSE;
#o_b_Done := TRUE;
#e_State := 0;
ELSIF #MB_MASTER_Instance.ERROR THEN
#o_w_Status := #MB_MASTER_Instance.STATUS;
#o_b_Error := TRUE;
#e_State := 0;
END_IF;
END_CASE;
6.3 DATA_ADDR Encoding for MB_MASTER
The S7-1200 Modbus library expects the Modicon-style 5-/6-digit base-1 address (400001 for holding register 0, 300001 for input register 0, 100001 for coil 0, 000001 for discrete 0). For Mastervolt holding registers the engineering address 0x0001 is therefore passed as 400001 + 0 = 400001 in the library. Adjust the offset to the actual address list you derived from the Mastervolt map.
7. Workaround B: External Modbus Gateway That Speaks FC23
If atomicity is a hard requirement (for example because the SCADA simultaneously writes the absorption voltage and reads the state machine and you cannot tolerate the value rolling back), insert an external Modbus TCP/RTU gateway that exposes the chargers' full FC23 register map to the S7-1200 as separate FC03 and FC16 transactions. The gateway then reassembles them into FC23 toward the Mastervolt converter. Devices that can be configured for this include the HMS Anybus X-gateway Modbus TCP to Modbus RTU and the Red Lion CR3000 in custom-protocol mode. Configure the gateway's IDB/IDAL reassembly table to match the Mastervolt register map; the S7-1200 continues to issue plain FC03/FC16 and never knows FC23 is in play.
8. Workaround C: Front-End the S7-1200 With a S7-1500 or Third-Party Bridge
If a S7-1500 CPU (or an ET 200SP CPU) is acceptable as a bridging controller on the same subnet, it can run a custom FC23 implementation in its SCL/STRUCT, since the S7-1500's higher-performance serial point-to-point blocks give finer control over inter-frame timing and PDU construction. That bridge then publishes the charger data to the S7-1200 over PROFINET or Modbus TCP. This is the cleanest solution if your project is open to a small hardware change.
9. Timing and Performance Budget
At 19200 bit/s, one byte is 520.8 µs. A typical FC16 of 8 registers is approximately 11 + (8 × 2 + 9) = 36 bytes (request + response), so 36 × 10 / 19200 = 18.75 ms wire time. A FC03 of 16 registers is about 53 bytes, i.e. 27.6 ms. The inter-transaction delay adds 50 ms, the converter's own processing adds 30..80 ms. Therefore a single charger write-then-read cycle takes about 130..180 ms. With 8 chargers polled sequentially on a 1 s period budget, each gets 125 ms - tight but achievable. Drop the poll to 2 s for headroom, or move to a faster baud (115200 bit/s, supported by the converter with re-configuration) to halve the cycle.
10. Verification and Commissioning Steps
- Bring up the RS485 link between CM 1241 and the Mastervolt converter with no chargers connected. Use a Modbus RTU scanner (e.g. a laptop with Modbus Poll tool in read-only mode) to confirm that the converter responds to FC03 reads of registers 0x0001..0x0004 and returns sane default values.
- Connect one charger to MasterBus, terminate both ends, and power up. Confirm the charger appears with its configured Modbus slave ID (factory default 1; re-assign via the converter's web UI or DIP switch if you have multiple chargers).
- In TIA Portal, run the FB_MastervoltPoll instance online. Watch
MB_MASTER.DONEandMB_MASTER.ERRORfor each transaction. Common status codes:
| STATUS (hex) | Meaning | Likely Cause |
|---|---|---|
| 0x0000 | OK | Success |
| 0x80C8 | Illegal function | Slave does not support the FC; FC23 is the usual cause |
| 0x80C9 | Illegal data address | Address not in converter map; check IDB/IDAL register list |
| 0x80D1 | CRC error | Wiring, termination, or baud mismatch |
| 0x80D2 | Timeout | Slave not on bus, wrong slave ID, or no inter-frame delay |
| 0x80D8 | Parity / framing | Baud or parity mismatch (converter default is 8N1) |
- Force a known write (e.g.
IDAL.SetBulkVoltage = 14.40 V -> 1440) and verify the new value is held by the charger. Then power-cycle the charger and re-read: the value should persist if it is an IDAL (non-volatile) tag. IDB tags (volatile) revert on power-up. - Add chargers one at a time. Watch the cumulative poll time in a trace to confirm it stays inside the OB1 budget. If a cycle overruns, increase the OB1 period or reduce the number of polled registers per charger.
11. Diagnostic Checklist
| Symptom | First Check | Second Check |
|---|---|---|
| No response from any charger | Converter powered, RS485 A/B not swapped | Slave address 1..247 in the project, baud 19200 8N1 |
| STATUS 0x80D2 on first poll only | Inter-frame delay too short (3.5 char times ~ 2 ms) | Converter's silence timer; many converters need 10 ms minimum |
| STATUS 0x80C9 on write of IDAL tag | Tag is read-only at that address | Verify against the Mastervolt datasheet for that product family |
| Values change in PLC but charger ignores them | MasterBus cable inversion (CAN-H / CAN-L swapped) | Charger is in remote control disable; enable via front panel |
| Charger state reads back 0xFFFE | Charger is off-line on MasterBus | Check terminator and CAN wiring |
12. Recommended Pattern for New Projects
For new deployments where the design has not yet been frozen, two changes remove the FC23 pain entirely:
- Ask Mastervolt to put the converter in legacy mode (when available on the firmware you ordered) where the device exposes all IDB and IDAL tags as separate FC03-readable and FC16-writable registers, so the S7-1200 never needs FC23.
- If a new-build is acceptable, replace the MasterBus converter with a Modbus TCP variant that exposes the same register map over Modbus TCP. The S7-1200's
MB_CLIENTinstruction supports FC03 and FC16 natively over TCP and the timing is much easier to manage than RTU. The CM 1241 RS422/RS485 is no longer required; use the CPU's PROFINET port instead.
IDAL.SetBulkVoltage or IDAL.SetFloatVoltage from a PLC without an operator confirmation, current-limit clamping, and a watchdog that reverts to the last known good value on communication loss. Mastervolt chargers treat an out-of-range voltage setpoint as a hardware fault condition.13. Frequently Asked Questions
Does any firmware version of the S7-1200 support Modbus FC23?
No. The S7-1200 Modbus RTU library shipped with all firmware versions up to and including the G2 generation supports only function codes 01, 02, 03, 04, 05, 06, 15, and 16. The CM 1241 (order number 6ES7241-1CH32-0XB0) does not add FC23. Refer to the TIA Portal V21 Modbus_Master instruction documentation for the official list of supported function codes.
Can I split a FC23 transaction into a FC16 followed by a FC03 on the S7-1200?
Yes. Issue MB_MASTER with MODE = 16 (Write Multiple Registers) for the IDAL block, wait at least 50 ms for the converter to forward the write to the charger on MasterBus, then issue MB_MASTER with MODE = 3 (Read Holding Registers) for the IDB block. The only loss is the atomicity of the original combined transaction; for SCADA polling with a single master this is normally acceptable.
What is the default serial configuration of the Mastervolt MasterBus-Modbus converter?
The factory default is 19200 bit/s, 8 data bits, no parity, 1 stop bit (8N1), Modbus RTU. Slave address is DIP-switch selectable or set via the converter's web interface. Re-verify on commissioning with a Modbus RTU master tool before connecting the S7-1200, because a baud mismatch produces STATUS 0x80D8 (framing) on the S7-1200 side and is the most common first-day failure.
How long does one full poll cycle take for 8 chargers at 19200 bit/s?
Each write+read pair takes roughly 130 to 180 ms including inter-transaction delay and converter processing. Eight chargers polled sequentially therefore need about 1.0 to 1.5 seconds. If you need a 1 s cycle, reduce the registers per charger, raise the baud to 115200 bit/s (re-configure the converter), or move to Modbus TCP.
Is there a way to make the S7-1200 issue raw Modbus FC23 with free-protocol blocks?
Not reliably. The SEND_P2P and RCV_P2P free-protocol instructions on the CM 1241 give you control over bytes on the wire, but you must also enforce Modbus RTU inter-character and inter-frame timing (1.5 / 3.5 character times) and compute the CRC16 (polynomial 0xA001) per request and validate it on every reply. The complexity and the lack of automatic Modbus exception decoding make this approach less maintainable than splitting the transaction, and it is not officially supported by Siemens for Modbus RTU use cases.