S7-1200 SEND_P2P: Sending Constant Characters via CM1241 RS232 to Arduino
1. Overview
When a Siemens SIMATIC S7-1200 CPU (for example CPU 1214C DC/DC/DC, order number 6ES7214-1AG40-0XB0) is paired with a CM 1241 RS232 communication module (6ES7241-1AH32-0XB0) and the SEND_P2P instruction is used in TIA Portal, engineers frequently discover that the very first byte transmitted on the wire (SendBuffer[0]) is not the value they wrote into the DB field. It is the instance DB number that TIA Portal assigned to the SEND_P2P background data block. The instance DB number is a numeric identifier (not user data), and it changes every time the block is pasted into a new network, every time the project is recompiled, and every time the block is re-instantiated in a different part of the program.
This behavior confuses a downstream receiver (such as an Arduino Uno/Mega/Nano reading the RS-232 line) because it sees an arbitrary, version-dependent byte rather than a deterministic command tag. The fix is to overwrite the SendBuffer[0] slot with a constant of the engineer's choosing, ideally loaded once at first scan of the CPU and held until the SEND block is triggered. This article documents the exact preloading procedure, the latching rule for the REQ input, the receive-side parsing on Arduino, and the diagnostic checks that confirm the wire bytes are correct.
2. Prerequisites
| Item | Specification |
|---|---|
| S7-1200 CPU | 1214C DC/DC/DC, firmware V4.2 or later (V4.4 / V4.5 / V4.6 recommended for current TIA Portal versions) |
| RS-232 module | CM 1241 RS232 (6ES7241-1AH32-0XB0) — note: this is the RS-232 variant; the 6ES7241-1CH32-0XB0 is RS-422/485 and is not pin-compatible |
| Engineering software | STEP 7 Basic / Professional in TIA Portal V15.1 or later; the PtP instructions are part of the standard instruction palette |
| Receiver | Arduino Uno R3, Mega 2560, Nano, or any board with a hardware UART and 5 V tolerant RX pin (3.3 V boards need a level shifter, see §8) |
| Cable | DB9 female to DB9 male, TX/RX crossed (null-modem), or jumper wire from CM1241 pin 3 (TX) to Arduino pin 0 (RX) and CM1241 pin 2 (RX) to Arduino pin 1 (TX); do not wire pin 7 (RTS) of CM1241 unless hardware handshake is enabled |
| Baud / parity | Both sides must match: e.g. 9600-8-N-1 (the most reliable Arduino Serial.begin() default for noise-sensitive car-park installations) |
| Documentation | SIMATIC S7-1200 Programmable Controller System Manual (entry ID 109741593), section on Point-to-Point communication |
3. Why SendBuffer[0] Looks Random
The SEND_P2P instruction lives in the "Communication > Point-to-Point" branch of the TIA Portal instructions palette. When you drop it into a network, TIA Portal creates an instance data block (IDB) with a default name like SEND_P2P_DB_1, SEND_P2P_DB_2, etc. The instance DB number (DB number) is allocated in the order the blocks were inserted, but it is not guaranteed to be sequential after a recompile, a project upgrade, a download, or a hardware reconfiguration. The DB number is a numeric tag (for example DB 1024) that the compiler may renumber if the program is reordered.
Because the instance DB number is what TIA places into SendBuffer[0] as the first byte of the payload when the SEND block fires, the byte the Arduino reads is volatile across engineering revisions. A car-park control where SendBuffer[0] = "elevator to floor 2" may become "elevator to floor 3" after a routine firmware update of the PLC — exactly the kind of silent failure that creates a safety issue with a moving car lift.
There are three robust solutions:
- Preload the buffer with a constant in OB100 (FirstScan OB) — recommended for this use case.
- Use a single SEND block with a multi-byte buffer — recommended when the messages are short and time-coincident.
-
Use the
SEND_CFGinstruction with explicit user data and avoid touchingSendBuffer[0]— applicable when full TCON/TSEND-style communication is available (CPU V4.0+ with open user communication, not the classic PtP API).
4. Send_P2P DB Structure
Each instance DB of SEND_P2P contains the following user-visible members that matter for this article. The exact offset names appear in the DB once the block is instantiated; values shown are the V15.1+ definitions from the S7-1200 system manual.
| Member | Type | Direction | Meaning |
|---|---|---|---|
REQ |
BOOL | IN | Rising edge starts the send operation |
PORT |
PORT (UINT) | IN | CM identifier (e.g. 271 for the first CM 1241, varies with slot) |
BUFFER |
VARIANT | IN | Pointer to the actual data area; usually the SendBuffer member of the same DB |
LENGTH |
UINT | IN | Number of bytes to send (max 1024 on CM 1241) |
DONE |
BOOL | OUT | One-cycle TRUE when send completed successfully |
ERROR |
BOOL | OUT | TRUE if a send error occurred |
STATUS |
WORD | OUT | Error code (see §10) |
SEND_BUFFER / SendBuffer[0..1023]
|
ARRAY of BYTE | IN/OUT | Working buffer the SEND block transmits |
To see the full layout: open the instance DB in TIA Portal, switch the view to "Data view," and the structure above will appear with byte offsets. Confirm that SendBuffer is declared as ARRAY[0..1023] OF BYTE; do not change this declaration unless you are ready to recompile the project.
5. Preloading the Buffer in OB100 (Recommended Method)
OB100 ("Startup") runs exactly once when the CPU transitions from STOP to RUN. Anything written there persists through the first scan of OB1 and survives until the next STOP/RUN cycle. Use four MOVE_BLK (or four MOVE) instructions in OB100 to write the four constant tags into the four instance DBs:
// OB100 - FirstScan / Warm Restart
// Preload each SEND_P2P instance DB with a deterministic command tag.
MOVE B#16#A // 'move car from floor 1 to ground'
=> "SEND_P2P_DB_1".SendBuffer[0]
MOVE B#16#B // 'move car from floor 2 to ground'
=> "SEND_P2P_DB_2".SendBuffer[0]
MOVE B#16#C // 'move car from floor 3 to ground'
=> "SEND_P2P_DB_3".SendBuffer[0]
MOVE B#16#D // 'move car to lift/elevator'
=> "SEND_P2P_DB_4".SendBuffer[0]
The keyword B#16# is S7-1200 ST syntax for a byte literal in hexadecimal; equivalent decimal would be B#16#A = 10, B#16#B = 11, B#16#C = 12, B#16#D = 13. Choose any byte value that does not collide with framing characters (avoid 0x00 and 0xFF as they are sometimes used by half-duplex drivers to signal line-idle). The four values 0xA through 0xD are safe because they fall inside the printable ASCII range and are easy to read in a serial monitor.
If you prefer to load in OB1 instead, the same moves must be unconditional (no EN on a first-scan contact) and placed before the SEND block fires — but OB100 is cleaner because the writes happen once, not every cycle.
6. Latching the REQ Bit (Critical)
The REQ input on SEND_P2P is edge-triggered internally, but the block must see a stable TRUE for the duration of the actual transmission. If the REQ signal is derived from a momentary input and the input drops before DONE or ERROR is asserted, the CM 1241 will hold the message in its internal FIFO and may lock the port until the next power cycle. The field-proven pattern is a self-latching coil:
// OB1 network that triggers SEND_P2P_DB_1
A "Input_Request_Floor1_to_Ground" // momentary pushbutton or HMI flag
S "REQ_Latch_DB1" // latched request, never drops
A "SEND_P2P_DB_1".DONE
O "SEND_P2P_DB_1".ERROR
R "REQ_Latch_DB1" // unlatch when send completes
// Then feed the latched bit to the SEND block:
A "REQ_Latch_DB1"
= "SEND_P2P_DB_1".REQ
Equivalent structured text:
IF "Input_Request_Floor1_to_Ground" THEN
"REQ_Latch_DB1" := TRUE;
END_IF;
IF "SEND_P2P_DB_1".DONE OR "SEND_P2P_DB_1".ERROR THEN
"REQ_Latch_DB1" := FALSE;
END_IF;
"SEND_P2P_DB_1".REQ := "REQ_Latch_DB1";
STATUS output will read 16#8180 ("Port not configured") or 16#80C0 ("Send buffer overrun") even after a STOP/RUN transition. A full power cycle of the S7-1200 rack is the only hardware reset.7. Alternative: Single SEND Block, Multi-Byte Payload
If the four commands are always sent close together (within a few scan cycles) and the receiver can parse a short frame, use one SEND_P2P block and concatenate the tags:
// OB100 - assemble a 4-byte payload
MOVE B#16#A => "SendBuffer".Bytes[0]
MOVE B#16#B => "SendBuffer".Bytes[1]
MOVE B#16#C => "SendBuffer".Bytes[2]
MOVE B#16#D => "SendBuffer".Bytes[3]
// OB1 - send all four bytes in one telegram
"SEND_P2P".REQ := "REQ_Latch";
"SEND_P2P".PORT := 271;
"SEND_P2P".LENGTH := 4;
Where "SendBuffer".Bytes is a global DB of type ARRAY[0..1023] OF BYTE declared in the project, and the SEND_P2P instance DB's BUFFER input is wired to point at that global DB. This approach halves the port load, eliminates the multi-block latching problem, and lets the Arduino parse a single 4-byte frame:
// Arduino - parse 4-byte command frame
void setup() {
Serial.begin(9600);
}
void loop() {
if (Serial.available() >= 4) {
byte cmd1 = Serial.read();
byte cmd2 = Serial.read();
byte cmd3 = Serial.read();
byte cmd4 = Serial.read();
switch (cmd1) {
case 0x0A: moveCarFloor1ToGround(); break;
case 0x0B: moveCarFloor2ToGround(); break;
case 0x0C: moveCarFloor3ToGround(); break;
case 0x0D: engageElevator(); break;
}
}
}
8. CM 1241 RS232 Configuration in TIA Portal
- In the device tree, expand the S7-1200 rack, right-click the slot holding the CM 1241 RS232, and select "Properties."
- Open "Port configuration." Set:
-
Baud rate: 9600 (or match Arduino
Serial.begin()). - Parity: None.
- Data bits: 8.
- Stop bits: 1.
- Flow control: None (XON/XOFF or RTS/CTS only if the Arduino sketch implements it).
-
Baud rate: 9600 (or match Arduino
- Open "Hardware identifier" and note the identifier — TIA shows it as a hex constant (for example
271). This is the value thePORTinput of everySEND_P2Pblock must reference. - Compile and download. If the configuration download fails with "Port in use," check that no terminal program on the engineering PC is holding the COM port.
The full parameter set is documented in section 8.5 of the S7-1200 System Manual, edition 04/2024.
9. Arduino Receive-Side Checklist
| Check | Action |
|---|---|
| Voltage levels | Confirm Arduino RX is 5 V tolerant. A 3.3 V Due or ESP32 needs a MAX3232 or a simple resistor divider on RX only (do not divide TX from Arduino, the CM1241 RX accepts 5 V CMOS levels). |
| Ground reference | Pin 5 of the CM 1241 DB9 must be tied to Arduino GND. Floating grounds are the single most common cause of garbled bytes. |
| Baud rate |
Serial.begin(9600, SERIAL_8N1) must match the CM 1241 configuration exactly. The default Arduino Serial.begin(9600) is 8N1 so it usually works, but verify with a logic analyzer if bytes are intermittent. |
| Buffer flushing | The Arduino Serial ring buffer is 64 bytes on most boards; calls to Serial.read() after Serial.available() will not block, but rapid bursts from the CM 1241 (more than 64 bytes between loop() passes) will silently drop bytes. |
| Hex echo | For initial bring-up, write Serial.println(incomingByte, HEX) to the Arduino IDE Serial Monitor and confirm A, B, C, D appear in order. |
10. STATUS / Error Code Reference
| STATUS (hex) | Meaning | Corrective action |
|---|---|---|
0000 |
No error, idle | None |
7000 |
Send in progress | None, wait for DONE |
8180 |
Port identifier invalid or port busy | Re-check the PORT input; recompile hardware config; power-cycle rack |
8181 |
BUFFER pointer invalid | Confirm BUFFER points to a DB of type ARRAY OF BYTE, not a POKE'd address |
8182 |
LENGTH out of range (0 or > 1024) | Set LENGTH to 1 for a single-byte command frame |
8183 |
Hardware configuration mismatch | Re-download the CM 1241 configuration; confirm baud/parity/stop bits |
80C0 |
Send buffer overrun — REQ dropped before DONE | Implement the latching pattern from §6; power-cycle rack |
80C1 |
Line physically disconnected (XON/XOFF or carrier lost) | Check cable, check GND, check that Arduino is powered |
These codes are from the S7-1200 system manual "Point-to-Point communication" section; they are the same for SEND_P2P, RCV_P2P, SEND_RST, and RCV_RST.
11. Verification Procedure
-
Static check (TIA Portal): Open each instance DB in data view, manually type
16#A/16#B/16#C/16#DintoSendBuffer[0], monitor online, and confirm the values hold (they will revert if the OB100 moves overwrite them on every scan — confirm OB100 runs only at startup, not every cycle). -
Online trace (Watch table): Create a watch table with the four
SendBuffer[0]tags and theDONE,ERROR,STATUSoutputs. ForceREQto TRUE and confirm a singleDONEpulse within one or two OB1 cycles. -
Wire-level check (logic analyzer or oscilloscope): Probe CM 1241 pin 3 (TX). The first byte on the line should be a start bit (low) followed by the bits of
0x0Ain LSB-first order:0 1 0 1 0 0 0 0 1(start + data + stop). A USB logic analyzer (Saleae, Sigrok/PulseView, or a $5 CP2102-based dongle with sigrok-cli) decodes the frame automatically. -
Arduino end: Open the Arduino IDE Serial Monitor at 9600 baud. Trigger each
REQfrom TIA (force TRUE in the watch table). The Serial Monitor should printA,B,C,Dfor the four blocks respectively. -
Functional check (car park): Run the elevator through one full cycle, confirm the car stops at the requested floor, and that no spurious intermediate stops occur. A spurious stop is a sign that an old or duplicate
REQlatch is still TRUE; clear all latches with a STOP/RUN transition and re-test.
12. Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
| Arduino sees different byte on every project download | Instance DB number is being used as SendBuffer[0]
|
Move constants into OB100 per §5 |
| First send works, second send locks the port | REQ drops before DONE/ERROR | Implement latch in §6 |
STATUS = 8183 after first send |
Hardware config not downloaded to CM | Right-click CM 1241 → "Download to device" → "Hardware configuration" |
| Arduino sees only 0x00 or 0xFF | Line idle character with no preloading or floating TX | Confirm TX pin 3 is driven; check that SendBuffer[0] is non-zero |
| Bytes arrive corrupted (every 4th bit flipped) | Baud rate mismatch by a factor of 2 | Confirm Serial.begin(9600) matches the CM 1241 baud; clock drift on a 16 MHz Arduino is < 0.2 % at 9600 baud, so this is rare |
| Nothing received at all | TX/RX not crossed, or GND missing | CM 1241 pin 3 (TXD) to Arduino RX (pin 0); CM 1241 pin 2 (RXD) to Arduino TX (pin 1); CM 1241 pin 5 (GND) to Arduino GND |
| One send works, then a 2-minute delay before the next | Arduino Serial buffer is full because loop() is blocked by an delay(2000)
|
Refactor to non-blocking millis()-based timing on the Arduino side |
ERROR is TRUE on the very first call |
PORT identifier wrong (e.g. 272 instead of 271) |
Re-read the CM 1241 hardware identifier in the device properties; the value is decimal in the PORT input |
13. Engineering Notes and Field Caveats
OB100 vs OB101: OB100 is the "complete restart" (warm restart) startup OB. OB101 is the "hot restart" startup OB and runs only when the CPU is configured for hot restart (CPU 1214C does not support hot restart — only the S7-1500 and S7-300/400 lines do). For S7-1200, OB100 is the only relevant startup OB.
Send buffer re-use across cycles: The CM 1241 internal UART FIFO is 256 bytes. If four SEND_P2P blocks all fire in the same OB1 cycle with the same REQ, only the first message is guaranteed to be on the wire immediately. The other three queue in the FIFO and are transmitted in sequence. For deterministic timing, separate the triggers by one OB1 cycle or use one multi-byte payload per §7.
Why not use TCON/TSEND (open user communication)? On CPU firmware V4.0 and later, the S7-1200 supports TCON/TSEND/TRCV as ISO-on-TCP or TCP. That protocol is connection-oriented, has a 4-byte TPDU header, and is not appropriate for a simple byte stream to an Arduino. The SEND_P2P PtP path remains the right tool for raw RS-232 byte transfer to a microcontroller.
Firmware pinning: If the project must run unchanged across multiple service visits, pin the CPU firmware version in the TIA Portal device configuration (Properties → General → Firmware version). This prevents TIA from silently upgrading the target firmware on download and possibly renumbering internal DBs.
EMC for a car park: The VFD-driven elevator motor in a multi-floor parking lot is a strong source of conducted and radiated noise. Run the RS-232 cable in shielded twisted pair, ground the shield at the cabinet end only, and add a 120 Ω termination if the cable exceeds 10 m. The CM 1241 has on-board TVS diodes on the RS-232 lines, but they are not a substitute for proper cable shielding.
Safety interlocks: The SEND-to-Arduino link should never be the primary safety stop for the elevator. Keep the safety chain (cat. 3 / PL d per EN ISO 13849-1) hardwired through safety contactors and a certified safety relay (for example a Siemens 3SK1 or Pilz PNOZ s5). The Arduino is a convenience controller for sequencing, not a safety controller.
14. Frequently Asked Questions
Why does SendBuffer[0] change every time I download the project?
It is the instance DB number that TIA assigns to the SEND_P2P background data block. The compiler may renumber it on every compile. Preload SendBuffer[0] with a constant in OB100 (§5) to fix the byte value independent of the project layout.
Can I write SendBuffer[0] from OB1 instead of OB100?
Yes, as long as the writes are unconditional and happen before the SEND block is triggered. OB100 is preferred because it runs once at startup and avoids burning PLC scan time on a static value.
What STATUS code means the CM 1241 port is locked?
STATUS = 16#80C0 means a send-buffer overrun caused by REQ dropping before DONE. STATUS = 16#8180 means the port is not configured or the identifier is wrong. A power cycle of the S7-1200 rack is the only hardware reset for a locked port.
Do I need a level shifter between CM 1241 RS232 and Arduino?
Only if the Arduino is 3.3 V (Due, Zero, ESP32, or any 3.3 V board). The CM 1241 RS232 swings ±5 V to ±15 V on TX and accepts 5 V CMOS on RX. A 5 V Arduino (Uno, Mega, Nano 5V) can connect directly with TX/RX/GND crossed; tie pin 5 of the CM 1241 DB9 to Arduino GND.
How many SEND_P2P blocks can share one CM 1241 RS232?
There is no hard limit from the instruction; all blocks use the same PORT identifier. In practice keep the count small (4 to 8) because each block needs its own REQ latch and a unique constant in SendBuffer[0]. If you need more than 8 distinct commands, switch to a multi-byte payload (§7) or add a second CM 1241 module.