☰
AF框架状态机在S7-1200/1500中的实现:从SCL编程到顺序控制实战
2026/9/27 23:22:58 网站建设 项目流程

AF框架(Application Framework)在西门子S7-1200/1500的圈子里,算是绕不开的一套SCL开源框架。我最近在整理它文档的翻译笔记,刚好把第九章节的内容在博途里跑了一遍,所以顺手把这章的翻译心得、代码细节和踩过的坑整理出来。这一章讲的是状态机(StateMachine)与顺序控制的框架实现,也就是很多人问的“怎么用AF框架把设备流程拆清楚”。它解决的问题很典型:一台设备十几步动作,用置位复位线圈写逻辑,改一步就牵连全局,最后没人敢动程序。AF框架的第九章给出的处理方式,是把流程拆成离散状态,用统一的状态字和迁移条件来管理,逻辑清晰、可扩展、现场调试也直观。

这份内容适合正在用TIA博途写S7-1200/1500程序、但被复杂工艺流程和频繁修改折磨的工程师。不管你是刚接触SCL,还是已经有几年项目经验,只要能看懂基础的SCL语法,跟着后面的步骤在博途里建一个测试项目,基本就能把这套思路用到自己项目里。第九章在AF框架整个体系里属于“承上启下”的位置:前面章节解决了数据类型、IO架构和报警设计的问题,从这一章开始,程序才算是真正进入了“设备动作管理”的层面。

1. 第九章在AF框架整体架构里的定位

1.1 AF框架到底解决了什么问题

AF框架的初衷很简单,就是把PLC程序从“一张图纸谁都不敢动”变成“一个个小功能块可以独立修改”。它把所有逻辑封装成带接口的FB,用专门的UDT(用户自定义数据类型)来封装数据,尽量避免直接裸用M区和临时变量满天飞的写法。这种思路在S7-300时代其实也有人用,但那时候受限于硬件资源,做得很苦。S7-1200/1500的计算能力和存储空间上来了,SCL的编辑体验也好了一大截,AF框架这种结构化写法的优势才真正被放大。

我在实际项目里的体会是,AF框架最大的收益不在“节省几KB内存”,而在“让不同工程师能协作改程序”。以前别人写的梯形图,你不看注释根本不敢碰;但用了AF框架的块结构,数据都走接口,逻辑都封装在块内部,外部只看到输入输出参数,修改的边界就清晰了。第九章的状态机模型,正好是把这种“封装”理念从单个块上升到整机设备的层面,让整台机器的动作流程也变成了一套可以独立维护的组件。

1.2 为什么要专门啃第九章而不是直接从第一章看

很多初次接触AF框架的人,喜欢按文档页码从头翻到尾,我一开始也这么干,但效果不好。前面几章讲的是基础数据类型、IO信号处理和报警管理,内容比较枯燥,而且如果没有实际项目经验,看完了也不知道这些类型到底怎么配合。第九章就不一样,它讲的顺序控制和状态管理,是每个PLC工程师每天都头疼的事,代入感特别强。

当你理解了第九章的状态机写法,再回头去看前面的数据类型定义,会自然明白为什么要那样设计。比如状态字(StateWord)为什么采用DWORD而不是Struct,为什么报警块和控制块要分开,这些在状态机逻辑里都能找到答案。所以我的建议是,时间紧就先啃第九章,遇到不懂的类型定义再往前翻,这种“按需检索”的阅读方式在技术文档面前反而最高效。

另外,从框架演进的角度看,第九章的内容也最具复用价值。IO规划和报警组态在不同项目里差异很大,但设备动作管理的框架基本是通用的:手动模式、自动模式、暂停、急停恢复、报警响应,这些都是标准需求。把这一章吃透,等于掌握了一套可以跨行业套用的“设备控制底座”,换行业做项目时,底层的状态机不用重写,只需要改状态列表和迁移条件。

2. 核心设计思想:用状态模型替代“线圈思维”

2.1 AFC状态机的基本运行模式

第九章反复强调一个观点:不要把设备动作写成“动作A触发完成后再触发动作B”这种链条式逻辑,而是要把设备行为描述成“若干稳定状态+状态之间的迁移条件”。链条式逻辑的问题是,一旦中间某一步被报警或急停打断,整个链条的后续步骤都不会执行,恢复时你得写大量“假动作”来兜底。而状态机模型天然适合处理异常:不管设备处于哪个状态,只要急停信号来了,都无条件迁移到急停状态,恢复时根据当前状态决定下一步去哪里。

这里我先把状态机的基本运行套路说出来,后面实操部分再展示代码。状态机运行时,每个扫描周期只做三件事:判断当前状态下的迁移条件是否成立,执行该状态对应的动作,更新输出和状态字。这三个动作在一个FB内部完成,外部只看到“当前状态”和“请求迁移”这两个接口。所谓“迁移”,不外乎四种形式:条件自动跳转(比如温度到了就进入下一步)、操作员触发(通过触摸屏的按钮信号)、报警中断跳转(报警位触发跳入故障状态)、复位确认跳转(故障复位后回到初始状态)。

这套模型中最重要的概念是“优先级”。条件的优先级一定要分清楚:急停条件永远比自动运行条件优先级高,报警条件又比正常运行条件优先级高。我记得文档原文里给了一个很形象的比喻:状态机不是“贪吃蛇”,它不会按顺序一条道走到黑,而是像一个执勤的保安,每时每刻都在判断“现在最该干什么”。操作优先级需要在编程前就定出来,而不是在CASE语句里随手写,否则后面加需求时非常容易产生逻辑冲突。

2.2 第九章里几个关键的数据载体

翻译第九章时,我特别注意了里面反复出现的三个数据对象:状态字、状态ID、迁移条件结构。这三个对象看起来简单,却是整个状态机的骨架。

状态字在AF框架里一般是一个DWORD,按位划分成手动位、自动位、故障位、急停位等,用于上位机读取和外部块联动。用一个DWORD的好处是可以通过一条点指令直接传给触摸屏或者OPC UA,不用打包多个变量。状态ID则是一个INT,用来标识设备当前处于具体哪个步骤,比如0代表空闲,100代表自动运行第一步,200代表故障停止。状态ID可以比状态字分得更细,我在项目里通常把它和触摸屏的“文本列表”绑定,这样WinCC或威纶通上就能直接显示“清洗中”“灌装中”之类的文字状态。

迁移条件结构是第九章里最有特色的设计。它把每一步的“触发条件”和“动作输出”都配置化,而不是写死在CASE分支里。实际工程里,很多设备的工艺流程经常微调,比如一个工位本来要等3秒,现在改成等5秒,如果这个超时值是写在CASE语句里的,改一次就要重新下载PLC程序。用配置化的迁移条件结构后,这些参数都放在全局DB里,上位机甚至可以直接通过触摸屏数值输入来修改,程序本身不用动,这对设备调试阶段的效率提升非常明显。

3. 实操:在TIA博途里把第九章的状态机搭起来

3.1 准备工作:库的导入和全局数据类型创建

要在自己项目里复用AF框架第九章的状态机逻辑,首先得把框架核心库导入博途。这里提醒一下,不同TIA版本的库文件不能直接通用,V15的库导入V17时偶尔会报版本不兼容,遇见这种情况不要硬来,去官网找对应版本重下。导入库之后,在项目树里展开“库→全局库”,把AF_StateMachine这个核心FB拖到“程序块”文件夹下,博途会自动帮你创建对应的背景DB接口。

建立全局DB时,我习惯把设备的状态参数集中存放,专门建一个“g_设备状态”的全局DB,里面放两类数据:一类是每个设备的状态ID和状态字,另一类是该设备状态机用到的所有可调参数,比如每步的超时时间、允许的偏差范围。这样做的好处是,现场调试时维修人员不用进FB内部修改逻辑,直接在全局DB的监控表里改参数就能调节工艺行为,降低了出错的概率。

还要创建几个UDT,包括“状态信息”类型,包含状态ID、状态字、时间戳、超时时间等,以及“迁移条件”类型,包含多个BOOL量和一个TIME量。这些类型在第九章的英文原稿里都有例子,我翻译时保留了原来的字段名,只是在注释里加中文说明。字段名不建议改成中文,因为后续SCL代码里会大量引用这些字段,用中文变量名也能编译,但在库文件跨项目拷贝和多语言团队协作时,中文名经常出幺蛾子。

3.2 搭一个三段式顺序控制的状态机代码

理论上的东西讲了不少,还是直接给一段能在博途里运行的SCL代码更实在。这个例子是三段式流程:设备上电复位,第一步气缸伸出到位,第二步等待3秒,第三步气缸缩回,然后回到初始。为了方便说明,代码里我简化了报警处理逻辑,只保留核心状态迁移骨架,实际项目里你在此基础上扩展状态和条件就行。

// 状态ID定义 // 0 空闲初始 // 10 自动启动 // 20 气缸伸出 // 30 等待完成 // 40 气缸缩回 // 999 故障停止 CASE "g_设备状态".状态ID OF 0: // 空闲状态,等待自动启动信号 IF "g_设备状态".自动请求 AND NOT "g_设备状态".急停 THEN "g_设备状态".状态ID := 10; END_IF; 10: // 自动启动,复位各类输出 #气缸伸出 := FALSE; #运行指示 := TRUE; IF "g_设备状态".启动确认 THEN "g_设备状态".状态ID := 20; END_IF; 20: // 气缸伸出动作 #气缸伸出 := TRUE; IF #气缸伸出到位 THEN #伸出到位时刻 := T_PLC_MS(); "g_设备状态".状态ID := 30; END_IF; 30: // 等待3秒 IF T_PLC_MS() - #伸出到位时刻 >= T#3S THEN "g_设备状态".状态ID := 40; END_IF; 40: // 气缸缩回 #气缸伸出 := FALSE; IF #气缸缩回到位 THEN "g_设备状态".状态ID := 0; #运行指示 := FALSE; END_IF; 999: // 故障停止,等待复位 #气缸伸出 := FALSE; #运行指示 := FALSE; IF "g_设备状态".故障复位 THEN "g_设备状态".状态ID := 0; END_IF; ELSE: "g_设备状态".状态ID := 999; END_CASE;

这段代码里用到的T_PLC_MS是S7-1200/1500的系统函数,返回一个64位毫秒时间戳,用来做短延时非常方便,比TON定时器块的代码量小。这里有个容易忽略的细节:在状态30里计算延时,必须用“伸出到位那一刻”的时间戳做基准,而不是进入状态30的扫描周期时刻。如果直接用进入状态的时刻做计算,会因为扫描周期的抖动导致延时每次都不一样,这在需要精确节拍的生产节拍应用里是致命的。

3.3 状态迁移表的设计手册和参数整定

AF框架第九章里最值得抄的其实是那张“状态迁移表”,它不是代码,而是一张用于和工艺工程师沟通的对照表格。我给每个设备做状态机之前,都会先在纸上把这张表画出来:左边三列分别是当前状态、触发条件、执行动作,右边两列是下一状态和备注。这张表每填一行,程序里的CASE分支就多一段,等逻辑讨论清楚了再动手写代码,比直接打开博途边想边写要省事得多。

表格做完后,很多参数其实不需要写死在程序里。比如“等待3秒”,在表格里我标注为可调参数,然后把这个参数映射到全局DB里某一个TIME变量上。触摸屏组态时,把该变量关联到“数值显示+数值输入”控件,操作员就可以在生产现场直接调整节拍,不用打开博途改程序。这样修改在技术上是安全的,因为程序结构没有变化,只是延时值变了而已,对现场工程师来说非常友好。

还要强调一点:每个状态机的超时时间必须单独成变量,不要共用一个全局TIME变量。我踩过这个坑,两个不同设备的状态机共享同一个超时时间,结果一个设备改参数另一个也跟着变,查了好久才发现是DB变量关联重复了。参数隔离是模块化编程的基本素养,也是AF框架从一开始就强调的设计原则。

4. 上位机、通讯和安全回路的联动方法

4.1 把状态字和状态ID抛给触摸屏和上位机

状态机做出来后,上位机的组态就变得很轻松。以威纶通触摸屏和S7-1200的通讯为例,你只需要在设备列表里新建一个MODBUS TCP或S7连接(具体看你用驱动类型),然后把全局DB里的状态ID变量映射到触摸屏内部的LW寄存器上,再在PLC里调用一条MOVE指令把状态ID转成字符串对应的数值。这里有个小技巧:状态ID最好按十进制位做规划,比如1-99是空闲/待机,100-199是自动流程步骤,200-299是报警,300-399是手动调试步骤,这样触摸屏上的文本列表可以按区段配置,条理非常清楚。

如果是S7-1500配上WinCC Unified或者传统的WinCC,操作更直接,因为TIA Portal集成环境下可以直接把DB变量拖到画面里,无需额外通讯配置。但国内现场大量使用的还是威纶通或昆仑通态这类第三方触摸屏,它们和S7-1200的标签导入功能现在已经很成熟,只要提前在PLC变量表里把状态ID的符号名起好,触摸屏软件能直接读取符号表导入,省去手动输入地址的功夫,也顺便避免了地址错误。

另一位同事的项目里用了Intouch和S7-1500做通讯,接的是S7-1500自带的OPC UA服务器。这种情况下状态机和上位机的对接更简单,上位机直接订阅“g_设备状态.状态ID”和“g_设备状态.状态字”这两个节点即可。需要注意,OPC UA浏览DB变量时,变量名如果是中文,部分老版本客户端软件会出现乱码现象,所以OPC公开的节点我建议都单独映射到一组英文变量上,尽量避免直接用中文符号名做通讯映射。

4.2 用状态机统一协调变频器、阀门和机器人

自动化线体上很少只有一套状态机,往往是多设备协同:变频器控制输送带,阀门控制流道,机器人在某个工位上下料。AF框架第九章的思路完全可以横向复制到这些执行机构上,每个变频器、每个阀门、每个机器人各自维护一个自己的状态机FB,再在上一级建一个“设备协调器”的FB来做状态同步。

举个我实际调试过的例子:输送带由ABB变频器驱动,PLC通过PROFINET和变频器交换控制字和状态字。变频器有两种控制方式,本地面板控制和远程总线控制,状态机里我把这两种方式分别建模成“本地待机”和“远程运行”两个状态。当急停或安全光栅触发时,PLC不是直接丢一个停止位给变频器,而是先把自身状态机切到“安全中断”状态,再由该状态下发“OFF2”(自由停车)控制字给变频器。这个转发逻辑保证了停止命令的优先级不会被自动运行状态下的启动命令覆盖,安全信号永远最后一锤定音。

机器人和PLC的PROFINET通讯地址对应,也是这套思路。机器人的每个工作流程用机器人侧程序定义好,PLC侧只维护“启动请求”“工作完成”“紧急停止”三个控制位和“机器人忙”“机器人故障”“当前工步”三个状态位。在PLC状态机里,机器人不是无脑发送所有命令,而是在不同状态下按需置位对应控制位。比如只有在“请求机器人抓取”状态下才允许把启动请求位置TRUE,在“安全中断”状态下强制把它清掉,这样就避免了机器人控制器同时收到多个互相矛盾的指令。

4.3 安全回路与muting功能块怎么融合进来

第九章状态机模型和安全PLC程序的结合,是很多做安全项目的工程师关心的点。S7-1200F和S7-1500F的安全程序一般用LAD/FBD来写,安全光栅、急停按钮这些信号进安全程序处理后,通过F-IO映射到标准程序区。在状态机里,我不建议把安全逻辑直接写在CASE分支里,而是建议在状态机FB的入口处加一个统一的安全门禁判断。

具体做法是,在FB的输入参数ISafeStop上接来自安全程序的急停输出。状态机每一轮扫描先判断这个位,如果为TRUE,不管当前状态ID是什么,都直接跳转到安全停止状态。这个跳转要放在Case语句外面做,也就是说,状态机FB的第一句就是“IF急停 THEN 强制进入停止状态”,而不是在每个CASE分支里逐个判断,否则总有漏网的状态没写急停响应。

muting功能块(安全光栅屏蔽)在AF框架里通常作为独立FB实现,它不直接参与状态机流转,而是输出一个MutingActive位。这个位的作用是在允许的时间窗内屏蔽安全光栅的触发信号,让物料可以正常通过。把muting功能和状态机的联动点放在状态迁移条件上,比如“在自动运行第3步,且muting激活时,光栅信号不作为迁移条件”,这种设计既符合安全标准的要求,又不会让安全PLC程序变得面目全非。安全程序里只保留muting功能本身的时间窗逻辑,联动的条件判断全部由状态机给出。

5. 常见编译坑和现场调试经验

5.1 编译报错的几个高频雷区

第一次把AF框架的状态机块拖到博途里编译时,如果报错,大部分都是环境问题,不是框架本身有问题。最常见的是“多重背景(Multi-instance)”声明错误,AF框架很多FB在设计时就采用了多实例背景数据块的模式,你在项目里如果把它声明为“单实例”或者“参数实例”,编译就会报错。解决方法是:调用AF状态机FB时,在FB的“多重背景”栏里勾选,或者使用一个专门的调用组织块,而不是在OB1里直接声明一个全局实例DB去调用。

另一个高频雷区是TIA博途版本差异导致的SCL语法不兼容。比如老版本里SCL支持直接给BYTE数组元素赋值,新版本会要求你使用POKE指令或者改用WORD位运算。AF框架库里大量使用位操作和数组,从老版本迁移项目到新版本时,这类报错几乎必然出现。我的经验是,遇到这类报错不要手忙脚乱,意思就是要你用更标准的系统函数替换掉原来的直接内存访问方式,翻译一下官方提示,按提示修改即可。

再一个坑就是保持性设置。状态机和普通逻辑不一样,它依赖当前状态ID来决策,如果PLC断电重启后状态ID变成了随机值,设备就会乱动作。所以全局DB里的状态ID、状态字,必须勾选“保持”属性。很多工程师定义全局DB时默认不勾保持,调试时一切正常,但现场一断电再上电,设备动作就错乱了。排查这类问题其实只要看程序块的属性面板,把需要掉电保持的变量勾上保持选项就行。

5.2 状态乱跳和输出抖动的排查套路

状态机投入运行后,最常被现场投诉的问题是“状态乱跳”或“输出抖动”。这两个问题的根源往往不是状态机逻辑本身,而是迁移条件的信号质量。比如用接近开关的到位信号做迁移条件,如果信号在临界点反复通断,状态就会在“正在执行动作”和“动作完成”之间来回跳,机械上表现为气缸抖动。解决办法是在迁移条件上加延时确认,也就是信号持续稳定的时间超过某个阈值(比如100毫秒)才认可到位。

输出抖动还有一个隐蔽原因:在外设还没有真正到位时,状态机就已经迁移到了下一拍。以液压系统为例,油缸伸出到位信号可能取自压力继电器,压力建立需要时间,如果你的状态迁移条件只检测到压力开关上升沿就立刻跳状态,下一拍的动作输出就会因为实际压力还没稳定而出现抖动。正确做法是,在状态迁移条件里同时加入压力和延时双确认,或者干脆在动作执行状态下增加一个最小保持时间。

在线监控状态机时,我推荐同时打开PLC的“调用结构”窗口和全局DB的监控表。调用结构窗口看的是FB是否被多个地方调用、调用顺序是否正确;全局DB监控表看的是当前状态ID和状态字实时值。遇到状态乱跳时,先看状态ID有没有瞬间跳好几位,如果出现跳过多位的情况,多半是有另一个FB也在改同一个状态ID,用交叉引用功能查一下“g_设备状态.状态ID”,很快就能定位到是哪个块在“捣乱”。这种多写冲突是AF框架应用中最容易混淆的地方,因为它不属于编译错误,只有在运行时才能发现。

5.3 调试工具和现场排错技巧实录

现场调试状态机,博途自带的“带有控制功能的仿真表”是最好用的工具。把状态ID、急停、复位、自动请求这些关键变量拖进仿真表,就能手动强制修改当前状态和触发条件,模拟各种极端情况,比如“设备正运行到第2步,突然按急停,程序会怎么走”。我在出厂前测试里一定会跑一遍所有状态的转换路径,确保每个迁移条件都被实际执行过,而不是只在理论上推演过。

还有一个很笨但对排查特别有效的方法:在每个状态分支里加上一个累加器,记录该状态被进入的次数。比如状态20分支里写“#状态计数[20] := #状态计数[20] + 1”,调试结束后看一眼这些计数,瞬间就知道每个状态被激活过几次。如果某个状态计数异常高,说明反复在这个状态和下一个状态之间打转,再结合定时器值就能判断是哪个条件在临界抖动。

最后分享一个关于在线修改程序的体会:状态机代码不像继电器逻辑那样改一笔就能生效。修改迁移条件或状态ID定义时,必须考虑正在运行设备的现实状态,比如修改“等待完成”分支的超时值,可以让状态机重新编译下载,但如果直接修改CASE分支结构,比如从30迁到40的条件,可能在下载瞬间让设备跳到一个不合理状态。所以我在现场在线修改状态机时,永远先把状态机切到“手动调试”模式,确认设备处于安全位置后再下载,下载完成后一路用仿真表把状态恢复到原流程,再切回自动模式。这套吃了几次亏总结出来的流程,我在项目里执行了两年,再没出过因为在线修改导致的状态错乱事故。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询