1. Problem Statement: SMS Send Contention on S7-1200
When the SIMATIC S7-1200 uses the Siemens SMS/GPRS library to deliver user alarms as Short Message Service (SMS) text messages over a CP 1242-7 or equivalent GSM/GPRS module, only one transaction can be in flight at a time. The library exposes a bBusy output that stays TRUE while the modem is transmitting, and a bDone / bError pair that releases the block on completion.
The collision pattern is straightforward: if digital inputs (DI0..DI7) toggle faster than the round-trip time of one SMS — typically 1.5 s to 6 s on a 2G/3G link — alarm triggers are lost because the rising edge occurs while bBusy = TRUE. The standard library does not include a multi-alarm buffer; the user must engineer one.
The constraints that shape the implementation are:
- The Siemens SMS/GPRS library does not accept an array of
STRINGas a payload. The S7-1200 DB editor forbids array-of-string construction. - Buffering raw alarm text in
STRINGvariables is wasteful; eachSTRINGoccupies 256 bytes minimum plus header overhead, blowing up the work memory budget of an S7-1200 (DBs are limited to 64 KB on CPUs below 1215C, 256 KB on 1215C+). - GSM PDU encoding of an SMS limits the user payload to 160 7-bit characters or 140 bytes for UCS-2. Encoding must be done in the application.
- The library handshake is asynchronous; the buffer manager must be a strict producer-consumer with deterministic overflow behaviour.
2. Prerequisites
| Item | Specification | Notes |
|---|---|---|
| CPU | S7-1211C / 1212C / 1214C / 1215C / 1217C | Firmware V4.2 or higher recommended for SMS library compatibility. |
| GSM modem | CP 1242-7 V2 (6GK7242-7KX31-0XE0) or CP 1243-7 LTE | Mounts left of CPU; uses RS-232-style internal bus. |
| Antenna | ANT794-4MR (GSM quad-band) | Required for CP 1242-7. |
| SIM card | Mini-SIM, 2G/3G capable | Disable PIN request before commissioning. |
| Engineering | TIA Portal V16 or later | SCL required for clean state-machine implementation. |
| SMS library | Siemens "SMS_send" FB or equivalent from Industry Online Support | Provides REQ, PHONE, TEXT, BUSY, DONE, ERROR, STATUS. |
Reference the S7-1200 System Manual for hardware configuration details and the CP 1242-7 manual for GPRS setup. Refer to the Siemens Industry Online Support portal for the exact SMS library version matching your TIA Portal release.
3. Architecture Overview
The buffer design uses three coordinated blocks:
-
DB_SMS_AlarmTable — a constant DB holding the alarm-code-to-text mapping. Index
iis the alarm code; entryiis the human-readable string. - DB_SMS_Buffer — a working DB containing the FIFO ring buffer, head/tail indices, count, and the live state-machine state.
-
FB_SMS_Dispatcher — an SCL function block that owns the state machine. It pulls the oldest alarm code from the buffer, looks up the text, hands the payload to the Siemens SMS library, and waits on
bBusyfalling.
4. Define the Alarm Lookup Table
Create a global DB named DB_SMS_AlarmTable and disable optimized block access (so you can read it from SCL by symbolic index without POKE_BLK).
// DB_SMS_AlarmTable - constant data block
// 16 alarm codes; codes 0..15
// Each entry is a STRING[80] (UTF-8 / ASCII payload)
STRUCT
szCode00 : STRING[80] := 'ALARM 0: System OK';
szCode01 : STRING[80] := 'ALARM 1: High pressure tank 1';
szCode02 : STRING[80] := 'ALARM 2: Low pressure tank 1';
szCode03 : STRING[80] := 'ALARM 3: Motor overload M1';
szCode04 : STRING[80] := 'ALARM 4: Motor overload M2';
szCode05 : STRING[80] := 'ALARM 5: Door interlock open';
szCode06 : STRING[80] := 'ALARM 6: Level switch tank 2 high';
szCode07 : STRING[80] := 'ALARM 7: Level switch tank 2 low';
szCode08 : STRING[80] := 'ALARM 8: Temperature out of band';
szCode09 : STRING[80] := 'ALARM 9: Power supply fault';
szCode10 : STRING[80] := 'ALARM 10: UPS on battery';
szCode11 : STRING[80] := 'ALARM 11: Communication lost';
szCode12 : STRING[80] := 'ALARM 12: Smoke detector';
szCode13 : STRING[80] := 'ALARM 13: Fire alarm';
szCode14 : STRING[80] := 'ALARM 14: Intrusion detected';
szCode15 : STRING[80] := 'ALARM 15: Operator call';
END_STRUCT
The decoder is a single CASE block in SCL. Keep the string widths uniform to make the CASE predictable.
5. Define the FIFO Buffer DB
// DB_SMS_Buffer - working data block
// Capacity: 32 entries. Power-of-two for cheap modulo via AND.
DATA_BLOCK DB_SMS_Buffer
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : OEM
FAMILY : SMS
VERSION : 1.0
STRUCT
// Ring buffer storage
aCodes : ARRAY[0..31] OF DINT; // Alarm codes waiting to send
wHead : INT; // Read index (consumer)
wTail : INT; // Write index (producer)
wCount : INT; // Number of pending alarms
wCap : INT := 32; // Constant capacity
// State machine
iState : INT; // 0=IDLE, 1=SEND, 2=WAIT, 3=OK, 4=ERR, 5=OVF
dwActive : DINT; // Code currently being sent
// Statistics
wSent : DINT; // Lifetime counter
wDrop : DINT; // Dropped-on-overflow counter
wErr : DINT; // Failed send counter
// I/O mirror of the SMS library
bBusy : BOOL;
bDone : BOOL;
bError : BOOL;
wStat : WORD; // STATUS word from library
// Configuration
bDropOldest : BOOL := TRUE; // TRUE=ring, FALSE=reject new
bEnable : BOOL := TRUE;
END_STRUCT;
END_DATA_BLOCK
S7_Optimized_Access := 'FALSE' only if you need symbolic array access from a third-party HMI. TIA Portal V17+ supports optimized arrays of DINT without this pragma; on older firmware, non-optimized access is mandatory for AT views.6. State Machine
The dispatcher runs once per OB1 cycle. The state diagram is:
| State | Value | Entry Action | Exit Action | Transition |
|---|---|---|---|---|
| IDLE | 0 | None | Pop alarm from buffer | → SEND when wCount > 0 AND bEnable |
| SEND | 1 | Copy alarm text, raise REQ on SMS FB | None | → WAIT immediately |
| WAIT | 2 | None | Reset REQ | → OK on bDone, → ERR on bError, → ERR on timeout (e.g., 30 s) |
| OK | 3 | Increment wSent, log to HMI | None | → IDLE |
| ERR | 4 | Increment wErr, capture STATUS | Optional: re-queue the alarm | → IDLE |
| OVF | 5 | Set alarm bit, increment wDrop | None | → IDLE after one cycle |
7. Producer: Pushing Alarms Into the Buffer
Each alarm input is debounced and edge-detected before being pushed. Use a rising-edge (FP) on each DI; map DI0→code 1, DI1→code 2, ..., DI7→code 8. Codes 9..15 are reserved for soft-alarms (tag-driven events from the user program).
// FC_SMS_AlarmSource - called once per cycle
// Reads 8 DIs, generates one rising edge per alarm,
// calls FB_SMS_Buffer_Push for each.
FUNCTION_BLOCK FB_SMS_Buffer_Push
VAR_INPUT
dwCode : DINT; // Alarm code to enqueue
bTrigger : BOOL; // Rising-edge source
END_VAR
VAR_OUTPUT
bAccepted : BOOL; // TRUE if pushed
bOverflow : BOOL; // TRUE if dropped
END_VAR
VAR
bPrev : BOOL; // Edge memory
END_VAR
BEGIN
bAccepted := FALSE;
bOverflow := FALSE;
IF bTrigger AND NOT bPrev THEN
IF "DB_SMS_Buffer".wCount < "DB_SMS_Buffer".wCap THEN
// Room available: write at tail, advance tail
"DB_SMS_Buffer".aCodes["DB_SMS_Buffer".wTail] := dwCode;
"DB_SMS_Buffer".wTail := ("DB_SMS_Buffer".wTail + 1) AND 31;
"DB_SMS_Buffer".wCount := "DB_SMS_Buffer".wCount + 1;
bAccepted := TRUE;
ELSIF "DB_SMS_Buffer".bDropOldest THEN
// Ring semantics: overwrite oldest, advance head AND tail
"DB_SMS_Buffer".aCodes["DB_SMS_Buffer".wTail] := dwCode;
"DB_SMS_Buffer".wTail := ("DB_SMS_Buffer".wTail + 1) AND 31;
"DB_SMS_Buffer".wHead := ("DB_SMS_Buffer".wHead + 1) AND 31;
"DB_SMS_Buffer".wDrop := "DB_SMS_Buffer".wDrop + 1;
bOverflow := TRUE;
ELSE
// Reject-new semantics: drop the new alarm
"DB_SMS_Buffer".wDrop := "DB_SMS_Buffer".wDrop + 1;
bOverflow := TRUE;
END_IF;
END_IF;
bPrev := bTrigger;
END_FUNCTION_BLOCK
8. Consumer: The Dispatcher State Machine
// FB_SMS_Dispatcher - owns the state machine, calls the Siemens SMS FB
FUNCTION_BLOCK FB_SMS_Dispatcher
VAR
iCycle : INT; // Cycle counter for timeout
szText : STRING[160]; // Resolved text payload (max 1 SMS)
tTimeout : TIME := T#30s;
END_VAR
VAR_TEMP
i : INT;
END_VAR
BEGIN
// Mirror library outputs each cycle for atomic reads
"DB_SMS_Buffer".bBusy := "SMS_lib".BUSY;
"DB_SMS_Buffer".bDone := "SMS_lib".DONE;
"DB_SMS_Buffer".bError := "SMS_lib".ERROR;
"DB_SMS_Buffer".wStat := "SMS_lib".STATUS;
CASE "DB_SMS_Buffer".iState OF
0: // IDLE
IF "DB_SMS_Buffer".bEnable AND
"DB_SMS_Buffer".wCount > 0 THEN
// Pop oldest
"DB_SMS_Buffer".dwActive :=
"DB_SMS_Buffer".aCodes["DB_SMS_Buffer".wHead];
"DB_SMS_Buffer".wHead :=
("DB_SMS_Buffer".wHead + 1) AND 31;
"DB_SMS_Buffer".wCount :=
"DB_SMS_Buffer".wCount - 1;
"DB_SMS_Buffer".iState := 1;
END_IF;
1: // SEND - resolve text, raise REQ
// Decode code to text using CASE
CASE "DB_SMS_Buffer".dwActive OF
0: szText := "DB_SMS_AlarmTable".szCode00;
1: szText := "DB_SMS_AlarmTable".szCode01;
2: szText := "DB_SMS_AlarmTable".szCode02;
3: szText := "DB_SMS_AlarmTable".szCode03;
4: szText := "DB_SMS_AlarmTable".szCode04;
5: szText := "DB_SMS_AlarmTable".szCode05;
6: szText := "DB_SMS_AlarmTable".szCode06;
7: szText := "DB_SMS_AlarmTable".szCode07;
8: szText := "DB_SMS_AlarmTable".szCode08;
9: szText := "DB_SMS_AlarmTable".szCode09;
10: szText := "DB_SMS_AlarmTable".szCode10;
11: szText := "DB_SMS_AlarmTable".szCode11;
12: szText := "DB_SMS_AlarmTable".szCode12;
13: szText := "DB_SMS_AlarmTable".szCode13;
14: szText := "DB_SMS_AlarmTable".szCode14;
15: szText := "DB_SMS_AlarmTable".szCode15;
ELSE
szText := 'UNKNOWN ALARM';
END_CASE;
"SMS_lib"(REQ := TRUE,
PHONE := 'PHONE_DB\'.szDest,
TEXT := szText);
iCycle := 0;
"DB_SMS_Buffer".iState := 2;
2: // WAIT - poll library handshake
"SMS_lib"(REQ := FALSE);
iCycle := iCycle + 1;
IF "DB_SMS_Buffer".bDone THEN
"DB_SMS_Buffer".wSent := "DB_SMS_Buffer".wSent + 1;
"DB_SMS_Buffer".iState := 0;
ELSIF "DB_SMS_Buffer".bError THEN
"DB_SMS_Buffer".wErr := "DB_SMS_Buffer".wErr + 1;
"DB_SMS_Buffer".iState := 4;
ELSIF iCycle * "OB1".CycleTime > tTimeout THEN
"DB_SMS_Buffer".wErr := "DB_SMS_Buffer".wErr + 1;
"DB_SMS_Buffer".iState := 4;
END_IF;
4: // ERR - log, optionally re-queue
// Optional: push dwActive back to head for retry
// IF <retryCount < 3> THEN
// PushBack("DB_SMS_Buffer".dwActive);
// END_IF;
"DB_SMS_Buffer".iState := 0;
5: // OVF - alarm latch
// HMI/warning lamp indicator - cleared by operator
"DB_SMS_Buffer".iState := 0;
END_CASE;
END_FUNCTION_BLOCK
9. Push-Back Helper (Optional Retry)
If the SMS network returned a transient error such as temporary SMSC congestion, the alarm should be retried. Implement a small LIFO push-back that re-inserts the active code at the head:
// Helper called from state 4
IF "DB_SMS_Buffer".wCount < "DB_SMS_Buffer".wCap THEN
"DB_SMS_Buffer".wHead :=
("DB_SMS_Buffer".wHead - 1) AND 31;
"DB_SMS_Buffer".aCodes["DB_SMS_Buffer".wHead] :=
"DB_SMS_Buffer".dwActive;
"DB_SMS_Buffer".wCount :=
"DB_SMS_Buffer".wCount + 1;
END_IF;
10. Caller Wiring in OB1
// OB1 - main cycle
// 1. Read 8 DIs and push alarms 1..8
"Inst_Push_1"(dwCode := 1, bTrigger := "DI0" AND NOT "DI0_old");
"DI0_old" := "DI0";
"Inst_Push_2"(dwCode := 2, bTrigger := "DI1" AND NOT "DI1_old");
"DI1_old" := "DI1";
// ... repeat for DI2..DI7
// 2. Soft alarms (example)
"Inst_Push_Temp"(dwCode := 8,
bTrigger := "Tag_TempHigh");
// 3. Run the dispatcher once
"Inst_Dispatcher"();
11. Buffer Sizing and Sizing Calculator
Buffer capacity must be sized against the worst-case burst rate and the SMS round-trip time. The formula:
Capacity_min = ceil(Burst_rate_per_sec × RTT_sec × 1.5)
| Burst Rate (events/sec) | Typical RTT (sec) | Capacity (entries) |
|---|---|---|
| 0.1 (one alarm per 10 s) | 3 | 1 |
| 1 | 3 | 5 |
| 5 | 5 | 38 → round to 64 |
| 20 | 6 | 180 → split alarms or use larger CPU |
12. Configuration Parameters
| Symbol | Address | Type | Default | Purpose |
|---|---|---|---|---|
| bEnable | DB_SMS_Buffer.DBX | BOOL | TRUE | Master enable for dispatcher. |
| bDropOldest | DB_SMS_Buffer.DBX | BOOL | TRUE | TRUE=ring (overwrite oldest), FALSE=reject-new. |
| wCap | DB_SMS_Buffer.DBW | INT | 32 | Capacity (must be power of two for AND-modulo). |
| tTimeout | Local in FB | TIME | T#30s | Max WAIT state duration before declaring ERR. |
| wRetry | Local in FB | INT | 3 | Number of push-back retries per alarm. |
13. Diagnostic and Troubleshooting Matrix
| Symptom | Likely Cause | Diagnostic Step | Remedy |
|---|---|---|---|
| SMS not delivered, no error bit | SMS library not initialised | Check SMS_lib.STATUS <> 0 |
Re-init CP 1242-7; verify SIM PIN disabled. |
| Only first SMS sent, rest lost | Buffer not implemented | Monitor DB_SMS_Buffer.wCount
|
Add dispatcher FB. |
| wDrop counter climbing | Burst rate exceeds capacity | Compute Capacity_min from sizing table | Increase buffer or throttle alarm sources. |
| wErr counter climbing | SMSC errors / no signal | Read STATUS word; map per library manual | Check antenna, signal strength (AT+CSQ), SIM credit. |
| State stuck in WAIT | Library handshake not polled | Verify dispatcher is called every cycle | Place FB call in OB1, not OB100. |
| Two SMS for the same alarm | Push-back retry without dedupe | Check retry counter | Add "in-flight" flag for the code. |
| Array access compile error | Optimised vs non-optimised mismatch | DB attribute in Inspector | Match DB_SMS_Buffer pragma to SCL access mode. |
| Buffer DB invalid at startup | Retain not set | Check "RETAIN" tick on key tags | Set RETAIN on wCount, wHead, wTail, wDrop, wErr. |
14. Status Word Reference
The Siemens SMS/GPRS library returns a 16-bit STATUS word. Common values documented in the library help text:
| STATUS (hex) | Meaning | Recommended Action |
|---|---|---|
| 0000 | Idle / no transaction | None |
| 7000 | No job active | None |
| 7001 | Job running | None - normal during WAIT state |
| 7002 | Job finished OK | Set state to OK |
| 8080 | SMSC timeout | Retry once; if persistent, check SIM/network. |
| 80C0 | Modem not responding | Power-cycle CP 1242-7; check RS bus. |
| 80C1 | SIM not inserted / PIN locked | Disable PIN, reseat SIM. |
| 80C2 | No network registration | Check antenna, signal (AT+CSQ > 10). |
| 80C3 | PDU encoding error | Verify TEXT length <= 160 chars. |
15. Commissioning Procedure
- Compile the SCL blocks; resolve any array-bounds errors by setting the DB to non-optimised access.
- Download hardware config (CPU + CP 1242-7) and verify online with "Go online → Accessible devices".
- Watch table
DB_SMS_Buffer— confirmwCount= 0 andiState= 0 after download. - Force DI0 TRUE; verify
wCountrises to 1, then falls back to 0 as dispatcher pops it. - Monitor
SMS_lib.STATUS; expect 7001 → 7002 sequence on success. - Force a burst: toggle DI0..DI3 at 1 Hz for 10 s; verify no entries are lost (wCount briefly > 0).
- Pull antenna; force DI0 again; expect ERR state and
wErrincrement. - Restore antenna, clear the error counter via HMI button, verify recovery on next alarm.
16. Verification Checklist
- Buffer DB compiles with no warnings.
- Optimised access setting matches SCL access pattern.
- RETAIN set on count, head, tail, counters.
- Lookup table contains a CASE for every alarm code in use.
- Dispatcher called once per OB1 cycle.
- Timeout > 5× typical RTT (≈30 s on 2G).
- Burst test passes with no lost alarms and no overflow.
-
HMI diagnostic page shows live
wCount,wSent,wDrop,wErr. - Power-cycle test: alarms queued before power loss are still pending after restart (if RETAIN enabled).
- Status mapping documented for the plant's alarm-response procedure.
17. Field-Proven Caveats
- Array of STRING is illegal in S7-1200 DBs. Buffer integer codes, not strings. Resolve text via CASE.
- Don't put the dispatcher in OB100 (startup OB). It must run in OB1 (cyclic) to honour the library handshake.
- Don't poll bBusy in OB35 faster than the library's internal tick — you may read a stale TRUE while the SMS has actually failed.
-
Power-of-two capacity lets you replace
MODwithAND (Capacity - 1), saving CPU cycles. - Watch DB retention scope: if the SMS buffer is to survive power loss, mark wCount, wHead, wTail, wDrop, wErr as RETAIN; do NOT mark the dispatcher's iState as RETAIN — it must restart at IDLE on cold start.
- GSM SMSC number must be configured on the SIM; the Siemens library defaults to reading the SMSC from the inserted SIM.
- Multi-recipient: add a recipient array on top of the alarm array if multiple phone numbers must receive each alarm. Buffer becomes a tuple of (code, recipient-index).
18. FAQ
Why can't I store an array of STRING in the S7-1200 DB?
The TIA Portal DB editor on S7-1200 firmware does not support ARRAY OF STRING as a direct data type. Use a flat DB with individually declared STRING members and address them by symbolic name, or buffer an integer code and resolve to text via a CASE statement as shown above.
How do I size the buffer capacity?
Use Capacity = ceil(Burst_rate_per_sec × RTT_sec × 1.5). For a 1 alarm/sec burst with a 3-second SMS round trip, allocate 5 entries. Always round up to the next power of two for fast modulo via AND-mask.
How should the buffer behave when full?
Two policies are valid: overwrite the oldest entry (ring semantics, preferred for alarm logging) or reject the newest entry (preferred for safety-critical alarms where loss is unacceptable). Configure via the bDropOldest flag in DB_SMS_Buffer.
What happens if the SMS library returns ERROR or times out?
The dispatcher increments wErr, records the STATUS word, and returns to IDLE. With the retry helper, the failed alarm code is pushed back to the head of the buffer and retried up to wRetry times before being abandoned.
Can I run the dispatcher in OB35 instead of OB1?
You can, but the OB1 default 10 ms cycle is sufficient. Running it faster than the library's internal tick (~100 ms) provides no benefit and may read stale handshake flags. Keep dispatcher execution at the OB1 cadence.
How do I test the queue without a live SIM?
Use TIA Portal's PLCSIM or the SMS library's loopback mode (where supported) to simulate handshake transitions. Alternatively, force bDone TRUE briefly via a watch table to advance the state machine and confirm buffer pop behaviour.