S7-1200 SEND_P2P Port_Config: Mastering 9-Bit Mark/Space Protocol
Communicating with multi-drop power supplies, intelligent meters, and legacy field instruments that use a 9-bit protocol (8 data bits + 1 address/mark bit) is a recurring challenge on the SIMATIC S7-1200. The standard Send_P2P and Port_Config instructions do not expose a raw 9-bit data mode; instead, they let you toggle between mark and space parity to emulate the ninth bit. When a project requires alternating parity per frame (one byte with mark parity, the next five bytes with space parity), many first-time users trigger Port_Config from a free-running clock, hang the asynchronous state machine, and watch the DONE bit never return TRUE on the second send.
This reference rebuilds the entire flow from hardware through verification, exposes every parameter that the S7-1200 PtP runtime actually exposes, and provides a deterministic state machine that survives runtime reconfiguration without corrupting the transmit buffer.
1. Problem Overview: Why the DONE Bit Never Returns TRUE
The classic failure path on a CPU 1215C with a CB1241 (RS485) and CM1241 (RS422/RS485) running TIA Portal V13 SP2 looks like this:
- The user wants to send Frame A (first byte parity = mark, next five bytes parity = space) followed by Frame B (different parity pattern) to a bank of power supplies.
- Port_Config is triggered by a free-running clock (for example, a 100 ms pulse from a system clock bit or a TON instance).
- Send_P2P is wired with the clock as REQ, a six-byte buffer, and the DONE output tied directly back to the REQ of the next Port_Config call.
- Symptoms observed:
- The second Port_Config never reports DONE = TRUE.
- The transmitted telegram is wrong: either the parity never switches back to space, or the slave responds after only four bytes instead of six.
- The slave occasionally acknowledges Frame A, but only if the send happened to coincide with a successful Port_Config execution.
The root cause is almost never the parity value itself. It is the asynchronous, non-reentrant execution model of Port_Config and Send_P2P combined with a missing state machine. Both FBs run as multi-instance background jobs inside the PtP firmware; calling them faster than they can complete, or calling Port_Config while a Send_P2P is still in flight, drops the request silently and leaves DONE stuck at FALSE.
2. Prerequisites and Hardware Baseline
Before touching the code, verify the following hardware configuration. The data points below come from the SIMATIC S7-1200 system manual and the CM 1241 / CB 1241 module manuals hosted on the Siemens Industry Online Support portal.
| Item | Required Value / Catalog Number | Notes |
|---|---|---|
| CPU | 6ES7215-1xx40-0xB0 or later firmware 4.2+ | CPU 1215C with DC/DC/DC or DC/DC/RLY; firmware 4.2+ recommended for stability of PtP instructions |
| RS485 board | 6ES7241-1CH30-1xB0 (CB 1241) | Plug-in signal board, only one per CPU; supports RS485 half-duplex |
| RS422/RS485 module | 6ES7241-1AH30-0xB0 (CM 1241) or 6ES7241-1CH32-0xB0 | Communication module on the left bus; RS422 full-duplex or RS485 half-duplex |
| Software | TIA Portal V13 SP2 (minimum) or V14/V15/V16 with HSP for the modules above | V13 SP2 is the legacy baseline that ships with the affected project; newer versions add USS_MODBUS and a freeport library |
| Firmware | CPU firmware 4.2.x or higher | Firmware 4.0 had known issues where Port_Config returned STATUS = 16#80C8 during continuous cycling |
| Topology | Shielded twisted pair, 120 ohm termination at both ends for RS485 | Shield grounded at one end only |
| Power supply | 24 VDC ±5%, 1.5 A minimum CPU supply | PtP modules draw up to 220 mA each from the backplane |
3. Understanding the 9-Bit Protocol on an 8-Bit UART
A "9-bit" serial protocol is not a different physical layer; it is a software convention layered on top of a standard 8N1 UART. The protocol uses the parity bit slot of the UART frame as a discriminator:
- Mark parity (parity bit forced to 1): Marks the byte as an address byte in a multi-drop bus.
- Space parity (parity bit forced to 0): Marks the byte as data.
The receiver ignores every byte with the wrong parity (it expects only one address byte per transaction) and accepts the rest. This is how a master can address one of up to 256 slaves on a single RS485 pair without any extra handshake wires.
The S7-1200 PtP runtime supports this trick by exposing five parity modes in Port_Config (see Section 4). Mark and Space force the parity bit to a constant value, which is exactly what the 9-bit protocol needs. The catch: you cannot mix data and address bytes within a single UART frame, so the only way to transmit a multi-byte telegram like "one address byte + five data bytes" is to change the parity mode between sends — which is precisely the requirement that breaks naïve ladder logic.
4. Port_Config Instruction Reference
The Port_Config instruction reconfigures the active PtP port at runtime. It does not start a transmission; it only updates the baud rate, parity, data bits, and flow control for the next frame the port transmits or receives. The full input/output set is:
| I/O | Type | Meaning |
|---|---|---|
| REQ | BOOL | Rising edge triggers the configuration job |
| PORT | PORT (HW_IO) | Hardware identifier of the PtP port (e.g., the CM 1241 RS485 identifier) |
| PROTOCOL | UINT | 0 = PtP freeport (default), 1 = USS, 2 = Modbus master, 3 = Modbus slave |
| BAUD | UINT | Baud rate in bits/s (300 ... 115 200) |
| PARITY | UINT | 0=None, 1=Odd, 2=Even, 3=Mark, 4=Space |
| DATABITS | UINT | 7 or 8 (8 is required for 9-bit emulation) |
| STOPBITS | UINT | 1 or 2 |
| FLOWCTRL | UINT | 0=None, 1=XON/XOFF, 2=RTS/CTS (hardware) — RTS/CTS not available on CB 1241 RS485 |
| XONCHAR / XOFFCHAR | CHAR | Control characters if FLOWCTRL = 1 |
| DONE | BOOL | TRUE for one cycle when the port accepts the new settings |
| ERROR | BOOL | TRUE if the configuration was rejected |
| STATUS | WORD | 16#0000 = OK, 16#80C8 = parameter error, 16#80C9 = port busy with another job |
For the 9-bit protocol, the relevant values are:
- PARITY = 3 for the address byte (mark, parity bit = 1)
- PARITY = 4 for the data byte(s) (space, parity bit = 0)
- DATABITS = 8
- STOPBITS = 1
5. Send_P2P Instruction Reference
The Send_P2P instruction transmits the contents of a buffer through the PtP port. Key parameters:
| I/O | Type | Meaning |
|---|---|---|
| REQ | BOOL | Rising edge starts a new send |
| PORT | PORT | Hardware identifier of the PtP port |
| BUFFER | VARIANT | Pointer to the tag that holds the telegram. Boolean and Boolean arrays are not supported. |
| LENGTH | UINT | Optional override; default = tag length |
| DONE | BOOL | One-cycle TRUE when the telegram has left the UART FIFO |
| BUSY | BOOL | TRUE while the firmware is still transmitting the bytes |
| ERROR | BOOL | TRUE on hardware or parameter error |
| STATUS | WORD | 16#0000 = OK, 16#80C8 = bad parameter, 16#80D0 = port not configured, 16#80D1 = buffer address invalid |
Two practical consequences drive most of the symptoms in the original post:
- Buffer tags in optimized access (symbolic, "non-stored" memory) are not accepted by Send_P2P. If the telegram is declared in a global DB that was created with "optimized block access", BUFFER must point to a non-optimized area or to a copy in standard access memory. Otherwise STATUS = 16#80D1 is returned and DONE stays FALSE.
- BUSY falling edge is the real completion signal. DONE pulses for one OB1 cycle when the firmware accepts the request. If the application polls DONE but the OB1 is too fast, it can miss the pulse. Track (BUSY = FALSE AND ERROR = FALSE) after a Send_P2P was issued as the robust completion check.
6. Root Cause of the Original Symptoms
Mapping the failure modes from the source question to the instruction reference reveals four distinct bugs, any one of which is sufficient to corrupt the transmission.
| Symptom | Likely Cause | Fix |
|---|---|---|
| DONE = FALSE forever on the second Send_P2P | Port_Config is called with a free-running clock while the previous Send_P2P is still BUSY | Move the entire handshake into a state machine; never call Port_Config or Send_P2P from an unconditional clock |
| Telegram content is wrong (parity did not switch) | Port_Config returned DONE but Send_P2P was issued before the new parity was latched into the UART | Insert a one-OB1-cycle "settle delay" between Port_Config.DONE and Send_P2P.REQ, OR wait for the second OB1 scan after Port_Config.DONE rises |
| Slave replies after only 4 of 6 bytes | The slave's inter-character timer expired; the PLC took >3.5 character times between address and data because Port_Config was called in between | Configure the port for mark parity once, send all 6 bytes; on RS485 multi-drop you cannot reconfigure mid-telegram on the S7-1200 without breaking character timing |
| STATUS shows odd values like "16#80D0" | Port_Config was never executed, the port is still in its default configuration (8N1), so parity does not match what Send_P2P expects | Run Port_Config once at startup to a known mark-parity baseline, then toggle only when necessary |
7. State Machine Architecture
The only correct way to drive Port_Config and Send_P2P for an alternating-parity protocol is a deterministic state machine. The machine below is implemented in SCL (Structured Control Language) inside a function block; the equivalent in ladder logic is feasible but harder to read. Replace the placeholder tag names with those of your project.
| State (UDT enum) | Action | Next State Trigger |
|---|---|---|
| 0 = IDLE | Wait for start trigger from HMI or sequence generator | StartCmd = TRUE |
| 1 = CFG_MARK | REQ = TRUE to Port_Config with PARITY = 3 | PortCfg_DONE = TRUE AND PortCfg_ERROR = FALSE |
| 2 = CFG_SETTLE | Increment settle counter (≥ 1 OB1 cycle) | SettleCount >= 2 |
| 3 = SEND_ADDR | REQ = TRUE to Send_P2P with one-byte buffer (address byte) | Send_DONE OR Send_ERROR |
| 4 = CFG_SPACE | REQ = TRUE to Port_Config with PARITY = 4 | PortCfg_DONE = TRUE AND PortCfg_ERROR = FALSE |
| 5 = CFG_SETTLE2 | Increment settle counter | SettleCount >= 2 |
| 6 = SEND_DATA | REQ = TRUE to Send_P2P with five-byte data buffer | Send_DONE OR Send_ERROR |
| 7 = WAIT_REPLY | Start Rcv_P2P (or Receive_P2P) and a reply timeout counter | Rcv_DONE OR ReplyTimeout |
| 8 = DONE | Set OutputComplete = TRUE for one cycle | Always returns to IDLE |
| 99 = ERROR | Latch last STATUS word, raise Error flag | Ack from HMI |
8. Complete SCL Implementation
The following SCL is production-ready for the CPU 1215C / CM 1241 combination. Drop it into a function block named FB_PtP_Master, declare the instance as inst_PtP, and call it once per OB1 cycle. Tags shown with "i" prefix are instance-static.
// FB_PtP_Master — alternating mark/space parity for 9-bit protocol
// Inputs: i_StartCmd (BOOL), i_PortCfg (PORT), i_Address (BYTE), i_Data (ARRAY[0..4] of BYTE)
// Outputs: q_Busy (BOOL), q_Done (BOOL), q_Error (BOOL), q_Status (WORD)
FUNCTION_BLOCK "FB_PtP_Master"
VAR
iState : INT := 0;
iSettleCnt : INT := 0;
iReplyTimer : TON; // 100 ms reply timeout
iPortCfg : Port_Config; // multi-instance
iSend : Send_P2P; // multi-instance
iRcv : Receive_P2P; // multi-instance
iAddrBuf : ARRAY[0..0] OF BYTE;
iDataBuf : ARRAY[0..4] OF BYTE;
END_VAR
BEGIN
// --- Default outputs ---
q_Busy := (iState > 0) AND (iState < 8);
q_Done := FALSE;
q_Error := FALSE;
q_Status := 16#0000;
CASE iState OF
0: // IDLE — wait for start command
IF i_StartCmd THEN
iAddrBuf[0] := i_Address;
iDataBuf := i_Data;
iState := 1;
END_IF;
1: // CFG_MARK — configure port for mark parity (address byte)
iPortCfg(REQ := NOT iPortCfg.Busy,
PORT := i_PortCfg,
PROTOCOL := 0,
BAUD := 9600,
PARITY:= 3, // 3 = Mark
DATABITS := 8,
STOPBITS := 1,
FLOWCTRL := 0);
IF iPortCfg.DONE AND NOT iPortCfg.ERROR THEN
iSettleCnt := 0;
iState := 2;
ELSIF iPortCfg.ERROR THEN
q_Status := iPortCfg.STATUS;
iState := 99;
END_IF;
2: // CFG_SETTLE — give the UART two cycles to latch the new parity
iSettleCnt := iSettleCnt + 1;
IF iSettleCnt >= 2 THEN
iState := 3;
END_IF;
3: // SEND_ADDR — transmit the single address byte
iSend(REQ := NOT iSend.Busy,
PORT := i_PortCfg,
BUFFER := iAddrBuf,
LENGTH := 1);
IF iSend.DONE AND NOT iSend.ERROR THEN
iState := 4;
ELSIF iSend.ERROR THEN
q_Status := iSend.STATUS;
iState := 99;
END_IF;
4: // CFG_SPACE — switch to space parity for data bytes
iPortCfg(REQ := NOT iPortCfg.Busy,
PORT := i_PortCfg,
PROTOCOL := 0,
BAUD := 9600,
PARITY:= 4, // 4 = Space
DATABITS := 8,
STOPBITS := 1,
FLOWCTRL := 0);
IF iPortCfg.DONE AND NOT iPortCfg.ERROR THEN
iSettleCnt := 0;
iState := 5;
ELSIF iPortCfg.ERROR THEN
q_Status := iPortCfg.STATUS;
iState := 99;
END_IF;
5: // CFG_SETTLE2 — settle delay before data send
iSettleCnt := iSettleCnt + 1;
IF iSettleCnt >= 2 THEN
iState := 6;
END_IF;
6: // SEND_DATA — transmit the five data bytes
iSend(REQ := NOT iSend.Busy,
PORT := i_PortCfg,
BUFFER := iDataBuf,
LENGTH := 5);
IF iSend.DONE AND NOT iSend.ERROR THEN
iState := 7;
ELSIF iSend.ERROR THEN
q_Status := iSend.STATUS;
iState := 99;
END_IF;
7: // WAIT_REPLY — listen for the power supply's reply (timeout 100 ms)
iReplyTimer(IN := TRUE, PT := T#100ms);
iRcv(EN_R := TRUE,
PORT := i_PortCfg,
BUFFER := iReplyBuf,
LEN := iReplyLen);
IF iRcv.NDR OR iReplyTimer.Q THEN
iReplyTimer(IN := FALSE);
q_Done := TRUE;
iState := 0;
END_IF;
99: // ERROR — latch and wait for operator acknowledgement
q_Error := TRUE;
IF NOT i_StartCmd THEN
iState := 0;
END_IF;
END_CASE;
END_FUNCTION_BLOCK
The two critical details that turn this from "almost works" into "works":
- REQ := NOT iPortCfg.Busy / NOT iSend.Busy — this self-resetting REQ pattern prevents the instruction from being re-triggered while the previous request is still pending. It is functionally identical to capturing DONE into a static edge memory, but it survives OB1 restart.
- The two-cycle settle counter — without it, the UART can transmit the address byte with the new (space) parity because the parity register has not been latched yet. Two OB1 scans is enough on a CPU 1215C; on a CPU 1214C with a heavy OB1 you may need three or four.
9. Handling Inter-Character and Inter-Frame Timing
Most 9-bit protocols used by power supplies (TDK-Lambda, Delta, Meanwell intelligent slots, etc.) require the master to respect:
| Parameter | Typical Value | How to Achieve on S7-1200 |
|---|---|---|
| Inter-character gap | ≤ 1.5 character times (e.g., 1.5 ms @ 9600 8N1) | Send all bytes of a single frame in one Send_P2P call; never break a frame across calls |
| Inter-frame gap | ≥ 3.5 character times (e.g., 4 ms @ 9600) | Insert a TON with PT = T#5ms in the IDLE state before issuing the next transaction |
| Slave response delay | 5 ms ... 500 ms (model-dependent) | Use a TON with PT set to the longest expected slave turnaround; reset on NDR |
| Bus turnaround (RS485) | ≥ 1 ms after last TX before enabling RX | Insert a T#2ms delay after Send_P2P.DONE before triggering Receive_P2P |
10. Alternative Approaches
If the cycle time is too tight to reconfigure parity between frames, three workarounds exist:
10.1 Always-On Mark Parity, Slave Filters Bytes
Configure the port once at startup with PARITY = 3 (mark). The PLC sends every byte with the parity bit set to 1. The slave ignores the parity on data bytes (it accepts both mark and space data in most multi-drop power supply protocols), or the master pre-masks parity in software. This is the simplest approach and is acceptable for half-duplex RS485 where every byte is an address.
10.2 Two Separate Ports
Use the CB 1241 for the mark-parity frames and the CM 1241 for the space-parity frames. Each port is configured once at startup; the application selects which port to write to based on the telegram. Drawbacks: doubles the hardware cost and the wiring harness; not always possible if the slaves are on a single bus.
10.3 Use the Freeport Library (Siemens "PtP Freeport" Library)
From TIA Portal V14 onward, the PtP Freeport library (article ID 51804435 in the Siemens Industry Online Support) provides a Send_Freeport_P2P instruction that accepts an array tagged with a per-byte parity bit. The library writes directly to the UART's 9-bit shadow register (UCSTXDAT on the CM 1241 ASIC) and supports hardware-driven 9-bit framing without Port_Config. TIA Portal V13 SP2 does not include this library, so a firmware upgrade to V14 or later is required to use it.
11. Verification and Diagnostic Procedure
After commissioning, perform the following four checks. Any failure points to a specific layer of the stack.
11.1 Loopback Test
- Jumper TX+ to RX+ and TX- to RX- on the CM 1241 RS422 port (or TX/RX on CB 1241 RS485).
- Run the FB with PARITY = 3 for all 6 bytes.
- Verify Receive_P2P returns the same bytes that were sent. If not, the wiring or hardware identifier is wrong.
11.2 Parity Verification with Oscilloscope
- Probe the TX line of the CM 1241 with a scope capable of serial decode (PicoScope 2204, Saleae Logic Pro 8, or equivalent).
- Decode the waveform as 8M1 (mark) for the address byte and 8S1 (space) for the data bytes.
- Verify the parity bit is high on the address byte and low on data bytes.
11.3 Status Word Latch
Add a watch table in TIA Portal that monitors the instance DB of FB_PtP_Master. Poll q_Status every 500 ms. Expected values in normal operation: 16#0000 (IDLE), 16#0000 transient during transitions. Any persistent value other than 16#0000 indicates a fault:
| STATUS | Meaning | Corrective Action |
|---|---|---|
| 16#0000 | OK | Continue |
| 16#80C8 | Parameter out of range | Verify PARITY, BAUD, DATABITS values |
| 16#80C9 | Port busy with previous job | Check that REQ is not being retriggered; add the settle state |
| 16#80D0 | Port not configured | Run Port_Config once during startup |
| 16#80D1 | Buffer address invalid | Disable optimized access on the buffer DB, or copy to a non-optimized tag |
| 16#80D2 | Buffer length > max (1024 bytes for S7-1200) | Reduce LENGTH |
| 16#80D5 | Port configured for a different protocol (USS/Modbus) | Set PROTOCOL := 0 in Port_Config |
| 16#80E9 | Port not ready (CPU in STOP or firmware mismatch) | Check CPU RUN LED and firmware version |
11.4 Slave-Side Verification
If a serial monitor tool (e.g., a freeport sniffer on a laptop running Docklight, Hercules, or strace-style tools) can be inserted between the PLC and the slaves, verify the bytes on the wire match the expected telegram exactly. This is the single most reliable commissioning step.
12. Common Pitfalls When Migrating from TIA V13 SP2
Projects originally written for TIA Portal V13 SP2 frequently hit three issues when upgraded:
- Multi-instance syntax. Earlier versions required separate data blocks for each call of Port_Config and Send_P2P; V14+ supports multi-instances inside a parent FB. Migrating a multi-DB layout into a multi-instance FB can leave dangling REQ pulses if the instance data block is not regenerated.
- Optimized access. V13 SP2 defaulted to "standard access" for new DBs; V14+ defaults to "optimized access". The buffer for Send_P2P must remain in non-optimized memory.
- Library version skew. If the project references the "PtP" library from V13 SP2 and the firmware on the CPU is V4.4 or later, the library may report STATUS = 16#80E9 (firmware mismatch). Update the library reference to the version bundled with the active TIA Portal.
13. Edge Cases and Field-Proven Caveats
- RS485 direction control. The CB 1241 RS485 board handles direction control automatically when "RS485" mode is selected in the device configuration. If the project uses the CM 1241 in RS422 mode for full-duplex, the PLC must manually toggle the RTS signal via the SEND_CFG / RECEIVE_CFG instructions; otherwise the TX line stays enabled after Send_P2P returns and prevents the slave from responding.
- Long cables. At 9600 baud over 100 m of shielded twisted pair, the cable capacitance (~100 pF/m) can stretch the rise time enough that the slave samples the parity bit incorrectly. Lower the baud to 4800 or 2400 if the installation requires long runs.
- Optically isolated power supplies. Some industrial power supplies provide only ~1.5 kV isolation. If the PLC and the power supplies are on different ground references, add an RS485 isolator (Phoenix Contact PSI-MOS-RS485/FO or similar) to prevent ground loops that can flip the parity bit.
- Termination and bias on multi-drop. Only the two end devices should have 120-ohm termination. Bias resistors (fail-safe) should be enabled at exactly one node, ideally the master. Enabling bias at multiple nodes creates a voltage divider that can pull the line into the mark region during idle, defeating the space parity on the data bytes.
- Timing of OB1 vs. the PtP firmware. The PtP firmware runs at a lower priority than OB1. On a CPU 1215C with a 2 ms OB1 cycle, it is safe to assume that one Port_Config call followed by a Send_P2P call in the same cycle will execute in order, but the DONE pulse may not appear until the following OB1. Do not gate the next step on a single OB1 scan of DONE; latch DONE into a static edge memory and act on it.
FAQ
Why does my second Send_P2P never report DONE = TRUE on the S7-1200?
The PtP firmware processes one outstanding job per port. Calling Port_Config or Send_P2P while the previous job is still in the queue drops the new REQ and DONE stays FALSE. Replace the free-running clock with a state machine that waits for the previous job's DONE pulse before issuing the next REQ.
What are the valid parity values for the 9-bit protocol in Port_Config?
PARITY = 3 selects mark parity (parity bit forced to 1, used for the address byte). PARITY = 4 selects space parity (parity bit forced to 0, used for data bytes). DATABITS must be 8. The values 0 (None), 1 (Odd), and 2 (Even) are standard UART modes and do not produce the deterministic parity bit that 9-bit multi-drop requires.
Can I call Port_Config and Send_P2P in the same OB1 cycle?
Yes, but only when sequencing them inside a state machine. Trigger Port_Config first, wait for DONE, then call Send_P2P in the next cycle. Calling both in the same cycle without DONE confirmation corrupts the state machine. A two-cycle settle delay between Port_Config.DONE and Send_P2P.REQ eliminates the "parity did not switch" symptom.
How do I send a six-byte telegram that mixes mark and space parity without breaking the slave's inter-character timer?
On the S7-1200 you cannot reconfigure parity mid-frame without breaking character timing. Either send the entire six-byte frame in one Send_P2P call with a single parity setting, switch to two physical ports (one mark, one space), or upgrade to TIA Portal V14+ and use the PtP Freeport library that supports hardware 9-bit framing.
My Send_P2P returns STATUS = 16#80D1. What does that mean?
STATUS 16#80D1 means the BUFFER pointer is invalid. The most common cause is that the buffer DB is in optimized access mode. Send_P2P cannot read optimized symbolic tags; the buffer DB must be configured for standard (non-optimized) access, or the buffer data must be copied into a non-optimized tag before the call.
Does the CB 1241 RS485 signal board support the same Port_Config parameters as the CM 1241?
Yes for baud, parity, data bits, and stop bits. The CB 1241 does NOT support hardware flow control (RTS/CTS) — set FLOWCTRL = 0. The CB 1241 also does not support RS422 full-duplex; for full-duplex use the CM 1241 with the RS422/RS485 variant (6ES7241-1AH30-0xB0 or 6ES7241-1CH32-0xB0).
How can I measure the inter-character gap on the wire?
Use an oscilloscope or logic analyzer with serial decode. Set the trigger on the falling edge of the start bit, then measure the time between the stop bit of the address byte and the start bit of the first data byte. Anything longer than 1.5 character times (e.g., 1.5 ms @ 9600 baud) means the slave will reject the frame; anything shorter than 3.5 character times means the master will treat the next transmission as a continuation of the same frame.