干自动化这行的,提起FactoryIO应该不算陌生。它是一个基于工业场景的3D仿真平台,最大的好处是能让你在没有真实机械设备的条件下,把整套控制逻辑完整跑起来。而“智能仓储物流系统”这类项目,恰恰最能检验编程功底:堆垛机三轴联动、出入库输送线节拍、分拣站物料分配、上百个传感器信号互相牵扯,还要保证运行稳定不撞车。这里面的关键,不只是你会梯形图或者会SCL,而是你能不能搭出一个清晰可控的编程框架。
我见过很多学员和刚入行的工程师,一上来就用梯形图一行行铺逻辑,设备少还好,设备一多就开始原地爆炸:信号交叉、堵料、互锁漏写、模式切换跳变……最后变成代码补丁摞补丁。其实用梯形图配合SCL写一套仓储物流控制框架,完全可以把问题消灭在代码组织阶段。这篇文章我会把一个可落地的编程框架完整拆开,从控制对象、语言分工、状态机设计到联调排障,一步一步讲清楚。不管你是刚学PLC编程的学生,还是刚入行做非标设备调试的工程师,这套思路都能直接用。
1. 先把控制对象摸清楚:智能仓储物流到底在控什么
1.1 仿真不是跑动画,而是模拟设备的“手与眼”
FactoryIO这类仿真软件,本质上是一个虚拟物理世界。你放进场景里的传感器、驱动器和挡块都有真实的物理碰撞和信号反馈。如果只是为了看一段动画,那其实没有学习价值;有价值的部分在于:它输出的每一个传感器信号,都对应现实里光电开关或接近开关的通断;你写的每一个输出信号,也会真实驱动虚拟电机和气缸运转。这意味着,在FactoryIO里写好的整套PLC程序,设备落地后稍作IO映射调整,就能基本照搬。这也是为什么拿仓储物流这种综合场景做训练最有价值。
常见做法里,控制层PLC和仿真平台通过工业通讯协议完成数据交换,比如S7 TCP、Modbus TCP。也就是说,你可以在自己熟悉的组态软件里建工程、写逻辑,仿真平台只需要把输入输出信号绑定到位。这一步的核心,是必须搞清楚输入输出映射表。FactoryIO帮你模拟的是物理部分,PLC负责的是控制决策部分,两边必须对同一张IO表达成共识,否则后面所有逻辑都是空中楼阁。
1.2 仓储物流系统的四个核心控制对象
一段典型的FactoryIO智能仓储流程,大致包括:输送线上料、堆垛机取货入库、出库取货、分拣站分拣。我把它们拆开说清楚。
输送线系统
不管入料还是出料,输送线都是最先触发动作的设备。控制上主要是电机启停、方向切换、货物到位检测。传感器要布置在不同位置,防止前后货物追尾,也要给堆垛机提供“货物已到位”的准确信号。
堆垛机
这是整个系统里逻辑最复杂的设备。它有三个轴:水平行走、垂直升降、货叉伸缩。三个轴必须协同定位,取货放货时序不能乱,还涉及“此刻运行到哪个点位”“目标仓位在哪”这种坐标管理。在FactoryIO里,堆垛机往往有明确的位置信号,需要你通过电机输出信号配合传感器精确定位。
分拣站
出库后货物进入分拣工位,由气缸或摆轮把货物推向不同滑道。控制关键在节拍:来料检测→分拣到位→推料→复位,这个时序不能有重叠,一旦气缸还在推料时下一件货物又过来了,大概率堵料。
仓位与数据管理
严格来说这不是一个机械对象,却是整个系统的核心数据表。要在PLC里记录哪些仓位有货、哪些仓位空、每个仓位存放的是哪类货物。用梯形图写这个会非常痛苦,用SCL写数组和结构体就顺理成章。
1.3 控制需求的本质:模式、互锁、时序
智能仓储的控制逻辑,本质上就三件事。
第一是模式管理。系统必须有手动、自动、单步、复位等模式。手动模式用来调试单台设备,自动模式按预设流程连续运行,单步模式方便查流程卡在哪里,复位模式则负责把现场设备拉回安全初始状态。
第二是互锁与安全。比如堆垛机在水平移动时,货叉绝对不允许同时伸出;输送线启动前必须确认没有堵料;紧急停止信号必须切断所有动作输出。互锁是梯形图最擅长处理的,因为它本身就是从继电器电路演化而来的并行逻辑。
第三是时序。入库、出库、分拣,本质上都是多个设备之间的动作序列。这个序列如果全写在梯形图里,一层嵌套一层,后期看半年都理不清。所以我的建议是:设备级动作用梯形图,流程级时序用SCL状态机。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 梯形图与SCL的配合逻辑:两种语言到底怎么分工
2.1 梯形图的看家本事:搭互锁和手动回路
梯形图源于继电器控制电路,相当于把电气原理图搬进了PLC。所以凡是可以画成“某条件通,某输出动作”的逻辑,用它都非常直观。最大的优势是现场可读性——哪怕不熟悉程序的人,看着触点串并联也能大致明白逻辑关系。
在仓储场景里,设备启停、互锁、急停回路,就是梯形图的天下。比如两台输送电机,要保证正常情况下不能同时反转怎么处理?最简单的方法就是两边常闭触点互串。
这里举一个极简的互锁示意:
- 电机正转输出 = 正转启动按钮 与 反转未运行 与 无急停 与 堆垛机未动作
- 电机反转输出 = 反转启动按钮 与 正转未运行 与 无急停 与 堆垛机未动作
放在梯形图里,形态就是左右两条并联支路,每一条都串着另一条运行状态的常闭触点。一旦某一边开始运行,另一边马上被封锁,从逻辑上杜绝了同时导通的可能性。
2.2 SCL的核心主场:数据计算与流程状态机
SCL接近Pascal语言,可以声明变量、写IF/CASE/FOR,自然承担了三类工作。
第一,数据管理。比如仓位表可以用二维数组表示,每个元素存货物编号、类型、入库时间。用梯形图遍历数组基本是灾难,而SCL里一个FOR循环就能查完整个货架。
第二,状态序列。流程控制最怕一锅粥,SCL里用CASE把每个状态单独写一段,当前状态只执行当前段的动作,清晰无比。而且状态号直接用一个INT变量表示,调试时一眼就能看到系统卡在哪一步。
第三,故障诊断与超时监视。给每个动作设定最长执行时间,一旦超时,SCL里一条IF判断就能触发报警并停机。这类逻辑如果写在梯形图里,定时器嵌套定时器,特别绕。
2.3 双语言接口设计:桥梁是变量表和功能块
梯形图和SCL都工作在同一个PLC程序里,共享变量表。可以这样分工:
- 设备控制层:每个核心设备建一个FB,梯形图实现,负责具体IO动作与互锁
- 流程调度层:一个统一的状态机FC,SCL实现,负责自动流程的切换
- 数据管理层:一个数据块DB保存货位表、订单信息,SCL读写
- 手动自动切换:一段梯形图或SCL逻辑,把按钮和旋钮信号翻译成运行模式
关键点是接口变量的规划。规划好了,梯形图里调用SCL功能块,或者在SCL里调用梯形图写好的FB,都只是几行调用语句的事。这个框架一旦立起来,后面新增设备时只需要复制FB、改IO映射,流程调度层基本不用动。
3. 可落地的编程框架:从IO映射到流程状态机搭建
3.1 先把程序分成四层:IO、设备、流程、诊断
我一般把程序组织成四个层次。
物理IO映射层:把FactoryIO里的输入输出点对应到PLC内部标签。比如入库口光电传感器对应I0.0,堆垛机水平正转输出对应Q0.0。这一层通常单独放,目的是后续换设备、换信号点时只改这里。绝对不能把物理IO地址散落在业务逻辑里,否则排查问题时要翻遍整个程序。
设备控制层:每台设备一个FB。输送线控制块负责电机正反转、减速、停止、故障复位;堆垛机控制块内部包含三轴定位逻辑与货叉互锁。设备层的程序只关心单台设备的动作正确性,不关心整个工厂今天要跑什么流程。
流程调度层:这是整个框架的中枢,用SCL状态机实现。它根据模式、传感器信号和货物信息,决定此刻让哪台设备做什么动作。它调用设备控制层的接口,但不直接操作物理IO。
诊断与报警层:监视所有设备和流程状态,一旦出现超时、位置丢失、模式冲突,立即触发报警并给出停机或降级策略。
四层各司其职,排查时只要先看诊断层报警,再定位到流程层状态,最后顺着调用关系找设备层,问题基本能快速收敛。
3.2 状态机怎么设计:以堆垛机取货为例
堆垛机是仓储系统中最容易出状况的设备。一次取货流程一般是:行走到目标仓位→垂直到目标高度→货叉伸出→货叉上升取货→缩回→行走到出货口→放下货物。这一串动作换成状态机,每个状态就是做一件事、等一个条件。
状态定义可以这样:
| 状态号 | 含义 | 跳转条件 |
|---|---|---|
| S0 | 空闲等待 | 收到任务触发 |
| S1 | 水平行走至目标仓位 | 水平到位信号 |
| S2 | 垂直升降至目标高度 | 垂直到位信号 |
| S3 | 货叉伸出 | 伸到位信号 |
| S4 | 取货确认 | 货物检测信号 |
| S5 | 货叉缩回 | 缩到位信号 |
| S6 | 水平行走至出货口 | 水平到位信号 |
| S7 | 垂直升降至出货口高度 | 垂直到位信号 |
| S8 | 货叉伸出卸货 | 卸货完成信号 |
| S9 | 货叉缩回 | 任务完成,回S0 |
放到SCL里,就是非常清晰的CASE分支:
scl复制CASE stStacker OF
S0: // 空闲
IF bTaskStart THEN
nTargetRow := nRequestRow;
nTargetCol := nRequestCol;
stStacker := S1;
END_IF;
S1: // 水平行走
bXAxisRun := TRUE;
IF bXAxisArrive THEN
bXAxisRun := FALSE;
stStacker := S2;
END_IF;
S2: // 垂直升降
bYAxisRun := TRUE;
IF bYAxisArrive THEN
bYAxisRun := FALSE;
stStacker := S3;
END_IF;
S3: // 货叉伸出
bForkOut := TRUE;
IF bForkOutArrive THEN
stStacker := S4;
END_IF;
S4: // 取货检查
IF bGoodsDetect THEN
stStacker := S5;
ELSE
stStacker := S4;
END_IF;
S5: // 货叉缩回
bForkBack := TRUE;
IF bForkBackArrive THEN
stStacker := S6;
END_IF;
S6: // 水平行走至出货口
bXAxisRun := TRUE;
IF bXAxisArrive THEN
bXAxisRun := FALSE;
stStacker := S7;
END_IF;
S7: // 垂直升降至出货高度
bYAxisRun := TRUE;
IF bYAxisArrive THEN
bYAxisRun := FALSE;
stStacker := S8;
END_IF;
S8: // 货叉伸出卸货
bForkOut := TRUE;
IF bForkOutArrive THEN
stStacker := S9;
END_IF;
S9: // 任务完成
bStackerDone := TRUE;
stStacker := S0;
END_CASE;
注意,实际调试中状态号之间的跳转不能只依赖一个瞬时信号。位置传感器信号往往是一闪而过,如果扫描周期慢一拍,就会漏掉跳转条件。所以我的习惯是给位置到位信号加“持续确认”逻辑:对同一个到位信号取TON延时,比如20ms,确认稳定后再触发跳转。
3.3 梯形图模块怎么嵌进这个框架
梯形图在这个框架里不会消失,它主要做两层事情。第一是设备FB内部的IO处理和互锁,第二是安全逻辑,比如急停回路、模式切换的硬互锁。
例如在输送线控制块里,手动启动条件是:
code复制|--[I急停/常闭]--[I手动模式]--[I启动按钮]--[/自动运行]----(M电机启动)--|
自动模式下,流程调度层直接置位输送线启动这个接口变量。手动和自动两个来源最终都要经过同一个互锁链,再从梯形图输出到物理点。
这种设计的好处是,即使流程调度层偶尔脑抽,设备层的互锁仍然会兜底,不会出现物理上的危险动作。安全与动作先由梯形图保证,流程指挥归SCL,功能性和安全性分开,调试起来安心很多。
4. 完整实操拆解:从FactoryIO场景到PLC上电联调
4.1 FactoryIO场景配置和信号映射
第一步,在FactoryIO里加载一个标准的智能仓储物流场景,比如“立体仓库+输送线+分拣站”的组合。加载后不要急着写程序,先逐个查看场景里的输入输出信号表。通常分数字量输入、数字量输出,少数场景还带模拟量。
需要记录的信息有四个:
- 每个传感器的名称和作用位置
- 每个执行器的名称和动作方向
- 通信方式(S7 TCP通道的IP和端口,或Modbus TCP的寄存器地址)
- IO点数总数
我的习惯是先在Excel或纸上列一张完整的IO映射表,再打开PLC组态软件建变量表。比如FactoryIO里的“入库输送线入口光电”映射到PLC变量DI_Conveyor_InFeed,地址I0.0;堆垛机“水平正转输出”映射到DO_Stacker_XRun,地址Q0.0。表里每一行都写上备注,后面程序写成什么样,这张表就是唯一依据。
4.2 PLC变量表与数据块设计
变量表建议分三块。
物理IO区:DI_开头的输入、DO_开头的输出,和FactoryIO一一对应。
内部线圈区:M区变量,存放按钮、模式、设备状态。比如M0.0代表手动模式,M0.1代表自动模式,M0.2代表急停触发。
数据块区:DB_AutoData存自动流程数据,包括当前状态号、请求仓位、货物信息;DB_WarehouseMap存仓位表,用二维数组模拟货架。
仓位表在SCL里这样定义:
scl复制TYPE
TRackCell : STRUCT
bOccupied : BOOL; // 是否有货
nGoodsType : INT; // 货物类型
nGoodsID : INT; // 货物编号
END_STRUCT;
END_TYPE
VAR
warehouse : ARRAY[1..10, 1..5] OF TRackCell;
END_VAR
以后查找空仓位,一个两维循环就出来了,梯形图写这个会写到怀疑人生。
4.3 核心控制逻辑实操:入库流程怎么串起来
以“入库流程”为例,完整链路是:
- 货物到入库输送线入口,入口光电为ON;
- 输送线运行,货物向前输送,依次触发多个中间传感器,确认货物位置;
- 货物到堆垛机取货位,取货位光电为ON,输送线停止;
- 堆垛机执行取货任务,从取货位取货;
- 堆垛机水平行走、升降到目标空仓位,货叉伸出放货;
- 仓位表更新占用状态;
- 堆垛机回原点,流程结束。
用SCL写这段流程调度,核心就是状态机的推进。我贴一个简化版框架:
scl复制CASE nFlowStep OF
10: // 等待入库任务
IF bTaskIn OR bAutoStart THEN
nTarget := nNextFreeShelf();
nFlowStep := 20;
END_IF;
20: // 启动入库输送线
bConvStart := TRUE;
IF bConvGoodsArrive THEN
bConvStart := FALSE;
nFlowStep := 30;
END_IF;
30: // 请求堆垛机取货
stStacker := S1;
nStackerFrom := nPickPos;
nStackerTo := nTarget;
nFlowStep := 40;
40: // 等待堆垛机任务完成
IF bStackerDone THEN
nFlowStep := 50;
END_IF;
50: // 更新仓位表
bOccupied[nRow, nCol] := TRUE;
nFlowStep := 10;
END_CASE;
这里有个细节:nNextFreeShelf这个函数名我用的是伪代码,真正的SCL里可以做成一个返回数组下标的函数,内部用FOR遍历warehouse数组。如果找不到空位就返回0,同时置位“仓库已满”报警。
在实际调试中,状态号不能全用跳变值。比如20步的输送线启动,要等货物到位信号持续稳定后再切30步。如果货物刚触到传感器又弹开,状态机就直接跳飞了。加去抖是仓储物流程序里绕不开的必修课。
4.4 联调时的几个关键观察点
联调不是写完全部代码再上电,一定要分层分步。
先调IO映射:给每个数字量输出强制输出,看FactoryIO里对应设备是否动作。再检查输入,确认每个传感器在触发时,PLC变量是否同步变化。这一步不做好,后边全瞎。
再调手动模式:通过按钮逐个设备启停,观察正反转、互锁是否可靠。手动都调不稳,自动一定更乱。
最后调自动流程:先空载跑流程,再放货物跑。空载时重点看状态机是否按预期跳转,放货后重点看传感器时序和货物位置是否有冲突。
我的经验是,大部分联调时间都花在“传感器信号不理想”上。FactoryIO虽然是仿真,但传感器响应速度、货物尺寸、气缸动作时间都是模拟出来的,跟真实设备非常接近。不能用看动画的心态,要用调现场的心态面对。
5. 常见问题排查与避坑要点
5.1 信号映射不一致这类低级失误占了七成
实际调试中,70%的问题不是逻辑写错,而是IO映射对不上。比如FactoryIO里设置的传感器是上升沿触发,而PLC程序里用电平判断,位置信号就会时有时无;或者把输出映射错位,一启动电机转错方向。
排查方法很直接:在PLC里建一张IO状态表,把所有输入输出变量按固定顺序排列,然后逐个强制、触发、观察。对照FactoryIO信号名与PLC变量名,一眼就能看出哪个点没对上。我强烈建议在写梯形图之前,先花一小时把这张表列全,后面能省好几天。
5.2 堆垛机定位偏移:先查传感器,再查状态时序
堆垛机跑着跑着就偏了,是仓储项目最常见的噩梦。常见原因有三种。
第一,位置信号有毛刺,PLC采样到错误位置。解决思路是加滤波去抖,在梯形图或SCL里用TON延时确认,只有信号持续几十毫秒才认为有效。
第二,速度切换太粗暴。堆垛机从快速接近到定位点,如果直接给停止,惯性会冲过位置。一般用两段速:先快速接近,进入低速区后减速,到位后停止。FactoryIO里可以通过给电机输出不同频率或不同运动速度来模拟。
第三,异步流程抢地址。比如自动流程以为堆垛机已经回原点,实际上它还在S5,下一个任务就发过去了。解决方法是状态机每个跳转都要求上一步完成信号为TRUE,不能靠时间估算。
5.3 混编代码的维护注意事项
梯形图和SCL混着用,最忌讳的是把状态机逻辑同时写在梯形图和SCL里。一旦这么干,两边都要维护,改了一处忘了另一处,版本不同步就非常麻烦。
我的规则:流程状态机严格只在一个地方写,要么全SCL,要么全梯形图。设备级互锁和IO处理严格只在设备FB里写。接口变量单独建一张清单,注明是哪个块写入、哪个块读取,防止跨块误覆盖。
另外我强烈建议每个设备FB内部增加一个调试观察接口,把内部关键状态、当前状态号、最近一次报警码输出到诊断DB里。联调时直接观察这些变量,问题定位精度会提高很多。FactoryIO虽然能看到物理动作,但很多内部条件它是看不到的,只能靠诊断变量。
5.4 框架先行,功能只是填肉
最后说点个人经验。我见过太多工程师觉得搭框架是浪费时间,写代码嘛,直接堆梯形图更快。可项目一旦做到智能仓储物流这种规模,代码长度和调试时间是成正比的。没有框架的项目,到了后期每解决一个新问题就可能炸出三个旧问题。
我现在接手任何新项目,第一周基本不写业务逻辑,先把IO表建好、把设备块抽出来、把状态机框架搭好。后面写功能的时候,就是往固定格子里填空。这个习惯帮我在这类项目里把调试时间压到原来的一半以下。如果你正在被仓储物流仿真项目折磨,我建议从下一版程序开始,试着把框架先立起来,再考虑梯形图还是SCL。语言从来不是瓶颈,结构才是。
