跳到正文
Learn-SCL
SCL 示例库/SCL 报警管理:16 路报警、快照与确认

SCL 报警管理:16 路报警、快照与确认

用一个 Word 管理 16 路报警:快照比较找出新报警,确认按钮取上升沿,输出 Error / NewError / State。

进阶 S7-1200 S7-1500 报警 Word 位操作 快照 上升沿 1 块 / 74 行 可仿真 TIA 已验证

一句话:报警管理用一个 Word 存 16 路报警位:快照保存上次确认时的报警,NewBits := Alarms AND NOT Snapshot 找出新报警;确认按钮取上升沿,确认后 Snapshot := Alarms 并清 NewError。

导入到我的 TIA 项目

通过 TiaLink 把本示例的 1 个 SCL 块作为“外部源”导入你当前打开的 TIA Portal 项目:自动生成块、编译,并清理外部源。导入后不会自动保存;FB 示例导入后需要你在 OB 里调用。

TiaLink 设置

默认 http://localhost:8080。也可以在本页地址后加 ?tialink= 临时指定。

TiaLink 启动时若设置了 --api-token,这里要填相同的值;只保存在本机浏览器 localStorage,不会上传。

报警管理和边沿检测很像:HMI 要显示的不是“现在有没有报警”,而是“有没有新报警”“操作员确认过没有”。这段逻辑的核心不是 16 个 IF,而是一个“快照”——把上次确认时的报警位保存下来,再和当前报警位比较。

16 位报警字、快照字和新报警位图的对比:当前为 1 且快照为 0 的位是新报警
当前报警位与快照位做 AND NOT,得到 NewBits;确认后快照更新为当前报警。

概述

FB_AlarmManager 用一个 16 位 Word 表示 16 路报警,bit0 对应报警 1,bit15 对应报警 16。每个扫描周期:

  • Error:任意一位为 1;
  • NewBits = Alarms AND NOT Snapshot:当前为 1、快照为 0 的位,就是新报警;
  • NewError:只要出现过新报警就置位,直到操作员按确认;
  • State:0=正常、1=有报警、2=有新报警。

确认按钮 Ack 在内部取上升沿:按下的第一个扫描周期把 Snapshot 更新为当前报警,并清掉 NewError。这样同一个报警消失后再出现,仍然会被当成新报警。

原工程 Meldungen.scl 用 32 个 Word 和 HMI 确认字,并用 16 段 ELSIF 逐位比较;本页把它精简成一路 16 位报警字,保留“快照 + 新报警 + 确认 + 状态”的语义。%X0..%X15 的逐位写法放在“关键 SCL 语法”里对照。

你将学到

  • 用 Word 位图管理多路报警,而不是为每路写一个 IF;
  • 用快照比较找出“新报警”,而不是只看当前值;
  • 为什么确认按钮要取上升沿,不能直接用电平;
  • 报警消失时为什么要清快照位,以及 State 状态码怎么设计。

接口与变量

下表由构建器从 SCL 源码的接口声明自动生成;页面上展示的代码就是导入 TIA 的同一份源码。

FB_AlarmManager FB

区块名称类型说明
输入AlarmsWord
输入AckBool
输出ErrorBool
输出NewErrorBool
输出NewBitsWord
输出StateInt
静态SnapshotWord
静态AckPrevBool
静态ErrorIntBool
静态NewErrorIntBool
静态StateIntInt
临时AckPulseBool
临时RisingWord

程序结构

导入顺序以下表为准:被依赖的块/类型先导入;本页已按依赖排好。

  1. FB_AlarmManager FB 16 路报警汇总、快照与确认 · 74 行 · 4 段

FB_AlarmManager FB

16 路报警汇总、快照与确认 · 74 行 · 源文件 blocks/FB_AlarmManager.scl

查看 / 复制完整块源码
FB_AlarmManager.scl
1FUNCTION_BLOCK "FB_AlarmManager"
2{ S7_Optimized_Access := 'TRUE' }
3VERSION : 0.1
4   VAR_INPUT
5      Alarms : Word;   // 16 路报警位,bit0 = 报警 1
6      Ack : Bool;      // HMI 确认按钮(电平,内部取上升沿)
7   END_VAR
8 
9   VAR_OUTPUT
10      Error : Bool;    // 任意一路报警为 1
11      NewError : Bool; // 有报警在本次确认之后首次出现
12      NewBits : Word;  // 哪些位是“新报警”
13      State : Int;     // 0=正常 1=有报警 2=有新报警
14   END_VAR
15 
16   VAR
17      Snapshot : Word;    // 上次确认时的报警位
18      AckPrev : Bool;     // 确认按钮的上一周期状态
19      ErrorInt : Bool;
20      NewErrorInt : Bool;
21      StateInt : Int := 0;
22   END_VAR
23 
24   VAR_TEMP
25      AckPulse : Bool;
26      Rising : Word;
27   END_VAR
28BEGIN
29   REGION 报警汇总
30      // 任何一位为 1 就表示有报警;Word 与 16#0000 比较,避免逐位判断。
31      #ErrorInt := #Alarms <> 16#0000;
32   END_REGION
33 
34   REGION 确认上升沿
35      // 确认按钮只处理一次:按下的第一个扫描周期产生 AckPulse。
36      #AckPulse := #Ack AND NOT #AckPrev;
37      #AckPrev := #Ack;
38   END_REGION
39 
40   REGION 新报警与快照
41      // 当前为 1、快照为 0 的位就是新报警。
42      #Rising := #Alarms AND NOT #Snapshot;
43      IF #Rising <> 16#0000 THEN
44         #NewErrorInt := TRUE;
45      END_IF;
46 
47      // 消失的报警从快照中清除,下次再出现仍然算新报警。
48      #Snapshot := #Snapshot AND #Alarms;
49 
50      // 确认:把所有当前报警记为已看过,并清掉新报警标志。
51      IF #AckPulse THEN
52         #Snapshot := #Alarms;
53         #NewErrorInt := FALSE;
54      END_IF;
55 
56      // 输出当前还未确认的新报警位(确认后自然为 0)。
57      #NewBits := #Alarms AND NOT #Snapshot;
58   END_REGION
59 
60   REGION 状态与输出
61      IF NOT #ErrorInt THEN
62         #StateInt := 0;
63      ELSIF #NewErrorInt THEN
64         #StateInt := 2;
65      ELSE
66         #StateInt := 1;
67      END_IF;
68 
69      #Error := #ErrorInt;
70      #NewError := #NewErrorInt;
71      #State := #StateInt;
72   END_REGION
73END_FUNCTION_BLOCK
74 

FB_AlarmManager / 报警汇总 — 任意一位为 1 就是有报警

FB_AlarmManager.scl · 报警汇总
29   REGION 报警汇总
30      // 任何一位为 1 就表示有报警;Word 与 16#0000 比较,避免逐位判断。
31      #ErrorInt := #Alarms <> 16#0000;
32   END_REGION

目的:把 16 位报警字压缩成一个 Error 布尔值。

条件 → 动作:Alarms <> 16#0000 → ErrorInt := TRUE;否则 FALSE。

为什么:HMI 和后续联锁通常只关心“有没有报警”,不需要每路都判断。整字比较比 16 个 OR 更短,也不容易漏位。

注意:Word 是无符号 16 位,16#0000 是全 0;不要写成 Alarms <> 0 之外的其他类型常量,避免隐式转换歧义。

FB_AlarmManager / 确认上升沿 — 一次按下只确认一次

FB_AlarmManager.scl · 确认上升沿
34   REGION 确认上升沿
35      // 确认按钮只处理一次:按下的第一个扫描周期产生 AckPulse。
36      #AckPulse := #Ack AND NOT #AckPrev;
37      #AckPrev := #Ack;
38   END_REGION

目的:把 HMI 的确认电平转换成单扫描周期脉冲。

条件 → 动作:Ack=1 且 AckPrev=0 → AckPulse=TRUE;每个周期结束把 AckPrev := Ack。

为什么:HMI 按钮按下后通常会保持几个扫描周期。如果直接用电平,每个周期都会执行确认,NewError 会被反复清掉,操作员看不到“新报警”。

注意:AckPrev 必须是静态变量(VAR),不能放 VAR_TEMP;否则每个周期都重建,永远检测不到上升沿。

FB_AlarmManager / 新报警与快照 — 当前位与快照位做 AND NOT

FB_AlarmManager.scl · 新报警与快照
40   REGION 新报警与快照
41      // 当前为 1、快照为 0 的位就是新报警。
42      #Rising := #Alarms AND NOT #Snapshot;
43      IF #Rising <> 16#0000 THEN
44         #NewErrorInt := TRUE;
45      END_IF;
46 
47      // 消失的报警从快照中清除,下次再出现仍然算新报警。
48      #Snapshot := #Snapshot AND #Alarms;
49 
50      // 确认:把所有当前报警记为已看过,并清掉新报警标志。
51      IF #AckPulse THEN
52         #Snapshot := #Alarms;
53         #NewErrorInt := FALSE;
54      END_IF;
55 
56      // 输出当前还未确认的新报警位(确认后自然为 0)。
57      #NewBits := #Alarms AND NOT #Snapshot;
58   END_REGION

目的:找出“上次确认之后第一次出现”的报警位。

条件 → 动作:

  1. Rising := Alarms AND NOT Snapshot;只要 Rising <> 0,NewErrorInt := TRUE;
  2. 报警消失时清快照位:Snapshot := Snapshot AND Alarms;
  3. 收到确认脉冲:Snapshot := Alarms,NewErrorInt := FALSE;
  4. 输出 NewBits := Alarms AND NOT Snapshot。

为什么:快照只在确认时整体更新,平时只清“已经消失”的位。这样一个报警消失后再出现,仍然会被算作新报警;如果每次扫描都整体更新快照,就永远检测不到新报警。

注意:第 2 步和第 3 步的顺序很重要。先清下降位,再处理确认;确认后 NewBits 自然为 0。NewBits 放在最后计算,保证确认的同一周期就归零。

FB_AlarmManager / 状态与输出 — 把内部标志翻译成 HMI 状态码

FB_AlarmManager.scl · 状态与输出
60   REGION 状态与输出
61      IF NOT #ErrorInt THEN
62         #StateInt := 0;
63      ELSIF #NewErrorInt THEN
64         #StateInt := 2;
65      ELSE
66         #StateInt := 1;
67      END_IF;
68 
69      #Error := #ErrorInt;
70      #NewError := #NewErrorInt;
71      #State := #StateInt;
72   END_REGION

目的:给 HMI 一个稳定的 0/1/2 状态,而不是让 HMI 自己组合 Error 和 NewError。

条件 → 动作:

条件 State 含义
ErrorInt=0 0 正常
ErrorInt=1 且 NewErrorInt=0 1 有报警,已确认
ErrorInt=1 且 NewErrorInt=1 2 有新报警,未确认

为什么:状态码把“有没有报警”和“是不是新报警”两个维度合并成一个字段,HMI 只需要一个颜色/文本映射。

注意:原工程还有一个 State=3(未定义)分支,教学版删掉了。状态机的每个状态都应该有明确含义;保留“未定义”只会让 HMI 多一个无法处理的分支。

仿真验证:让 Agent 写场景并执行(不需要博途)

推荐:让 Agent 自己设计场景

不需要你手写 scenario。把下面的提示词复制给已连接 PLC 云仿真 MCP 的 AI 助手(Claude Code / Cursor / VS Code 等),它会阅读源码、自己设计正常/边界/异常场景,并调用 scl_run_scenario 执行;新用户有 100 次免费调用。

  1. 复制提示词(包含完整 SCL 源码和任务说明)。
  2. 粘贴给已配置 sim MCP 的 Agent,让它先 scl_validate,再执行自己写的 scl_run_scenario。
  3. 看 Agent 返回的 PASS/FAIL、输入/期望/实际;需要精确复现时,再对照下方本站回归基线。
AGENT PROMPT
你已连接 PLC 云仿真 MCP(sim-cloud)……

任务:阅读下面的 SCL 示例,自己设计 3 个场景并用 scl_run_scenario 执行:
1) 正常路径
2) 边界/临界值
3) 异常、互锁或超时
每个场景请给出输入、期望、实际结果和 PASS/FAIL,并说明场景设计理由。
不要照抄现成场景。

示例:SCL 报警管理:16 路报警、快照与确认
// 复制后包含完整 SCL 源码和 MCP 参数说明

页面上只显示提示词开头;复制的内容包含全部块源码和 MCP 参数说明。

查看本站回归基线(固定场景,不是让你手写)

下面是本站构建时跑过的固定场景和预期输出,用来做回归对比或精确复现;不是要求你手写 scenario。推荐先让 Agent 自己写场景,再和这份基线对照。

EXPECTED
PASS  第一路报警出现,确认后消失 (5 step(s), 5 scan(s))
PASS  同一个报警消失后再出现,算新报警 (4 step(s), 4 scan(s))
PASS  两路报警:确认只清新报警标志 (3 step(s), 3 scan(s))
RESULT: ALL PASS (12 scan(s))
BASELINE JSON
{
  "sources": [
    {
      "name": "blocks/FB_AlarmManager.scl",
      "source": "FUNCTION_BLOCK \"FB_AlarmManager\"\n{ S7_Optimized_Access := 'TRUE' }\nVERSION : 0.1\n   VAR_INPUT\n      Alarms : Word;   // 16 路报警位,bit0 = 报警 1\n      Ack : Bool;      // HMI 确认按钮(电平,内部取上升沿)\n   END_VAR\n\n   VAR_OUTPUT\n      Error : Bool;    // 任意一路报警为 1\n      NewError : Bool; // 有报警在本次确认之后首次出现\n      NewBits : Word;  // 哪些位是“新报警”\n      State : Int;     // 0=正常 1=有报警 2=有新报警\n   END_VAR\n\n   VAR\n      Snapshot : Word;    // 上次确认时的报警位\n      AckPrev : Bool;     // 确认按钮的上一周期状态\n      ErrorInt : Bool;\n      NewErrorInt : Bool;\n      StateInt : Int := 0;\n   END_VAR\n\n   VAR_TEMP\n      AckPulse : Bool;\n      Rising : Word;\n   END_VAR\nBEGIN\n   REGION 报警汇总\n      // 任何一位为 1 就表示有报警;Word 与 16#0000 比较,避免逐位判断。\n      #ErrorInt := #Alarms <> 16#0000;\n   END_REGION\n\n   REGION 确认上升沿\n      // 确认按钮只处理一次:按下的第一个扫描周期产生 AckPulse。\n      #AckPulse := #Ack AND NOT #AckPrev;\n      #AckPrev := #Ack;\n   END_REGION\n\n   REGION 新报警与快照\n      // 当前为 1、快照为 0 的位就是新报警。\n      #Rising := #Alarms AND NOT #Snapshot;\n      IF #Rising <> 16#0000 THEN\n         #NewErrorInt := TRUE;\n      END_IF;\n\n      // 消失的报警从快照中清除,下次再出现仍然算新报警。\n      #Snapshot := #Snapshot AND #Alarms;\n\n      // 确认:把所有当前报警记为已看过,并清掉新报警标志。\n      IF #AckPulse THEN\n         #Snapshot := #Alarms;\n         #NewErrorInt := FALSE;\n      END_IF;\n\n      // 输出当前还未确认的新报警位(确认后自然为 0)。\n      #NewBits := #Alarms AND NOT #Snapshot;\n   END_REGION\n\n   REGION 状态与输出\n      IF NOT #ErrorInt THEN\n         #StateInt := 0;\n      ELSIF #NewErrorInt THEN\n         #StateInt := 2;\n      ELSE\n         #StateInt := 1;\n      END_IF;\n\n      #Error := #ErrorInt;\n      #NewError := #NewErrorInt;\n      #State := #StateInt;\n   END_REGION\nEND_FUNCTION_BLOCK\n"
    }
  ],
  "scenario": {
    "docs": [
      "FB_AlarmManager.scl"
    ],
    "block": "FB_AlarmManager",
    "tickMs": 10,
    "seed": {
      "Alarms": "16#0000",
      "Ack": false
    },
    "always": [
      "!(NewError && State == 0)",
      "NewBits == 0 || NewError"
    ],
    "scenarios": [
      {
        "name": "第一路报警出现,确认后消失",
        "steps": [
          {
            "set": {
              "Alarms": "16#0000"
            },
            "expect": {
              "Error": false,
              "NewError": false,
              "NewBits": "16#0000",
              "State": 0
            }
          },
          {
            "set": {
              "Alarms": "16#0001"
            },
            "expect": {
              "Error": true,
              "NewError": true,
              "NewBits": "16#0001",
              "State": 2
            }
          },
          {
            "set": {
              "Ack": true
            },
            "expect": {
              "Error": true,
              "NewError": false,
              "NewBits": "16#0000",
              "State": 1
            }
          },
          {
            "set": {
              "Ack": false
            },
            "expect": {
              "Error": true,
              "NewError": false,
              "State": 1
            }
          },
          {
            "set": {
              "Alarms": "16#0000"
            },
            "expect": {
              "Error": false,
              "NewError": false,
              "NewBits": "16#0000",
              "State": 0
            }
          }
        ]
      },
      {
        "name": "同一个报警消失后再出现,算新报警",
        "steps": [
          {
            "set": {
              "Alarms": "16#0001"
            },
            "expect": {
              "NewError": true
            }
          },
          {
            "set": {
              "Ack": true
            }
          },
          {
            "set": {
              "Ack": false,
              "Alarms": "16#0000"
            },
            "expect": {
              "NewError": false
            }
          },
          {
            "set": {
              "Alarms": "16#0001"
            },
            "expect": {
              "NewError": true,
              "State": 2
            }
          }
        ]
      },
      {
        "name": "两路报警:确认只清新报警标志",
        "steps": [
          {
            "set": {
              "Alarms": "16#0005"
            },
            "expect": {
              "NewBits": "16#0005",
              "State": 2
            }
          },
          {
            "set": {
              "Alarms": "16#0005",
              "Ack": true
            },
            "expect": {
              "NewBits": "16#0000",
              "State": 1
            }
          },
          {
            "set": {
              "Alarms": "16#0007",
              "Ack": false
            },
            "expect": {
              "NewBits": "16#0002",
              "NewError": true,
              "State": 2
            }
          }
        ]
      }
    ]
  }
}

打开 PLC 云仿真

关键 SCL 语法

写法 含义
#Alarms : Word 16 位无符号字,bit0..bit15 对应 16 路报警
#Alarms <> 16#0000 整字比较,任意一位为 1 就为真
AND NOT 位与 + 位取反,用来求“当前为 1、快照为 0”
#Alarms AND NOT #Snapshot 得到新报警位图
#Snapshot := #Snapshot AND #Alarms 只保留仍然为 1 的快照位
%X0 / %X15 直接访问 Word 的某一位;偏移必须是字面量,不能写 %X#i
REGION ... END_REGION 代码分段,TIA 里可以折叠

原工程的逐位写法等价于:

SCL
1IF #Alarms.%X0 = 1 AND #Snapshot.%X0 = 0 THEN
2   #NewErrorInt := TRUE;
3END_IF;

16 路就要写 16 段;用 AND NOT 一行就能得到整张位图。

常见坑

  • 确认用电平:Ack 一直为 1 时每个周期都确认,NewError 永远留不住;必须取上升沿。
  • 每次扫描都整体更新快照:Snapshot := Alarms 放在新报警判断之前,NewBits 永远是 0。
  • 报警消失后不清快照:快照一直保持 1,同一个报警再次出现不会被当成新报警。
  • 把 AckPrev 放 VAR_TEMP:临时变量每周期重建,边沿检测失效。
  • 用 State=3 表示“未定义”:HMI 无法给出有意义的颜色/文本;状态码只保留有业务含义的值。
  • Word 位序搞反:bit0 是报警 1,不是报警 16;HMI 地址映射要统一。

变体与扩展

  • 报警超过 16 路时,用 Array[0..N] of Word,把每个字当成一组 16 路,外层再套一层 FOR。
  • 需要“每路单独确认”时,把 Snapshot 改成 Array[0..15] of Bool,确认时只更新当前选中的那一路。
  • 需要报警锁存/复位时,可以在 NewError 后面加一个 RS 或自锁,把“出现过的报警”保持到复位。
  • 需要报警文本/时间戳时,把 NewBits 的每一位映射到报警号,再交给 Program_Alarm 或 HMI 报警系统;本页只讲位图,不涉及报警组态。

常见问题

SCL 里怎么判断“新报警”?

把当前报警字和快照字做 AND NOT:当前为 1、快照为 0 的位就是新报警。快照只在确认时整体更新,或者报警消失时清掉对应位;这样同一个报警消失后再出现还能再次算新报警。

报警确认为什么要取上升沿?

确认按钮通常在 HMI 上按一下会保持几个扫描周期。如果直接用电平,每个周期都会执行一次确认,NewError 会被反复清掉;取上升沿后一次按下只确认一次。

验证状态

项目结果日期证据
本地仿真场景PASS(12 scans)2026-11-01页面下方 `scl_run_scenario` 请求可复现
PLC 云仿真PASS2026-11-01`scl_run_scenario`(托管 MCP)
TIA 导入编译PASS(0 error / 0 warning)2026-11-01本地 SCL 仿真与 PLC 云仿真(scl_run_scenario)12 扫描通过;TIA Portal V19 + TiaLink 1.4.0.0 导入 PLC_2,编译 0 错误 / 0 警告,已保存。

本地仿真引擎验证语法、结构和逐扫描行为;TIA 编译验证在 Windows + TIA Portal + TiaLink 上完成。两者不能互相替代。

来源与许可

本页示例改编自 github.com/lopez-dev/Siemens-SCL-Source-Files 的 Meldungen/Meldungen.scl,原作者 Daniel Cáceres López,以 MIT 许可发布。中文注释、接口调整、仿真场景与页面讲解由 Learn-SCL 编写。

原文件用 32 个 Word 和 HMI 确认字,并用 16 段 ELSIF 逐位比较;本站精简为一路 16 位报警字,保留快照、新报警、确认和状态语义。

示例源码以 MIT 许可发布(上游仓库);中文讲解与页面内容版权归汇智网(Hubwiz)。官方参考:Siemens TIA Portal。

把这个示例带进你的工程

TiaLink

把 TIA Portal 接到你的智能体:导入、编译、读回核对、PLCSIM 仿真。本地运行,工程数据不出厂。

了解 TiaLink →

PLC 云仿真

不装博途,把 SCL 放到云端逐扫描执行:校验、仿真、场景验收。新用户 100 次免费。

打开云仿真 →

Learn-LAD

同一个团队的梯形图示例库:逐网络讲解、变量表、TiaLink 一键导入。

浏览 LAD 示例 →