那段时间我正在客户现场做系统联调。仓库总面积6000多平,8排货架,接近4500个货位,堆垛机一跑起来,头顶上货物唰唰移动,地面输送线上的托盘匀速前进,这种规模的项目第一眼确实冲击力很强。项目核心是一套基于西门子S7-1500PLC的大型立体仓库控制系统,负责两台堆垛机和整条输送机系统的全部逻辑:入库、出库、移库、盘点、异常回退,全部由PLC程序调度,WCS只负责任务决策和库位管理。这套程序在后来的半年里稳定运行,几乎没有因为PLC逻辑导致停机。这篇博文就把这套系统的设备组成、程序架构、关键逻辑和调试过程整理出来,给正在做智能物流项目的工程师一个参考,也给那些在学校做比赛、做仿真但没见过真实现场的朋友一个“实际项目到底长什么样”的答案。
1. 从现场设备聊起:立体仓库不是只有堆垛机那么简单
1.1 项目规模与核心指标
这套仓库是典型的第三方物流配送中心场景,货架区8排,每排双深位,总共接近4500个货位,库高12米,单托盘载重最大1吨。系统里有两台双立柱堆垛机,分别负责左半区和右半区的出入库作业,地面输送系统由入库输送线、出库输送线、分合流段、外形检测站、称重站、输送机接货站台组成,总长接近200米。
先看一组关键指标,这组数据决定后面所有程序逻辑怎么写:
- 堆垛机水平行走最高速度:180m/min(约3m/s)
- 堆垛机提升机构最高速度:40m/min
- 货叉伸缩速度:60m/min
- 水平定位精度:±5mm,垂直定位精度:±3mm
- 单台堆垛机理论循环时间:约110秒/托(含取货、放货全程)
- 系统设计出入库能力:55托/小时
这些指标在行业里属于中高端配置。水平3m/s意味着堆垛机从巷道一端跑到另一端只需要10秒出头,而惯性非常大,停车定位必须在非常短的距离内从3m/s降到0,这对PLC的位置控制算法、变频器动态响应、机械结构刚度都是考验。定位精度正负5毫米听起来不难,但在12米高的货架上、双立柱结构晃动的情况下,实际做起来远比想象中麻烦。
1.2 为什么用西门子1500PLC而不是300或400
这个项目在方案阶段其实纠结过控制器选型。老工程师的习惯是S7-300,因为过去十年立体仓库项目基本是300的天下,程序库现成、人员熟悉、售后方便。但新项目我们坚持用了S7-1500,原因有几个:
第一,性能差距是代际性的。S7-1500的CPU处理速度比S7-300快了几十倍,特别是位运算和浮点运算。堆垛机控制需要频繁计算位置差、速度、加减速距离,300虽然也能跑,但在程序量大了之后扫描周期会很紧张。CPU 1516-3 PN/DP的位运算速度以纳秒计,整个堆垛机程序加输送机程序跑一轮只要几毫秒。
第二,S7-1500自带运动控制功能。1500的PLCopen运动控制指令可以直连G120变频器做定位控制,不需要额外运动控制器,这在老300时代是做不到的。300配G120做定位只能自己写通讯和算法,麻烦而且精度受限。
第三,TIA Portal的工程体验和调试效率。1500从硬件组态到程序编写、在线诊断、Trace功能一体化,特别是Trace功能,抓速度曲线和位置曲线太方便了,这在我们后面调试堆垛机减速点的时候帮了大忙。
为什么不选400?400的定位是大型DCS和极端冗余场景,本项目的IO点数在3000点以下,1500的资源绰绰有余,400的硬件成本高出一截,而且400的软件体系偏旧,新写的程序库很难迁移过去。
1.3 硬件组态与网络结构
主控制器采用CPU 1516-3 PN/DP,分布式IO使用ET200SP。堆垛机本体上的IO模块、变频器通过拖链网线接入主干PROFINET网络,输送机区域的IO则按区域分散布置了5个ET200SP从站。网络拓扑做了MRP环网,避免单点断线导致全线通信瘫痪。
主要硬件配置如下表:
| 硬件 | 型号/规格 | 数量 | 用途 |
|---|---|---|---|
| CPU | 1516-3 PN/DP | 1 | 主控制器 |
| 分布式IO | ET200SP IM155-6 PN ST | 6 | 堆垛机2个+输送线4个 |
| 堆垛机水平变频器 | G120 PM240-2,22kW | 2 | 水平行走驱动 |
| 堆垛机提升变频器 | G120 PM240-2,30kW | 2 | 起升驱动 |
| 货叉伺服 | S210 + 1FL6伺服电机 | 2 | 货叉伸缩定位 |
| 输送机变频器 | G120C PM230,1.5kW | 18 | 各输送段驱动 |
| 激光测距仪 | SICK DL100 | 2 | 堆垛机水平认址 |
| 绝对值编码器 | SICK AT60 | 2 | 堆垛机垂直认址 |
| 光电传感器 | SICK W12/W16系列 | 46 | 输送线货物检测 |
| 条码扫描枪 | 基恩士 SR-1000 | 2 | 入库口/出库口条码读取 |
程序架构上,除了主OB1之外,还用了OB30做100ms循环中断,用于堆垛机高速运行时的位置采样和速度给定;OB82做诊断中断;OB121/OB122做编程错误和IO访问错误处理。核心逻辑封装在FB块里:FB100堆垛机控制、FB200输送机控制、FB300任务管理、FB400通信接口,每个FB配独立的背景DB,互不干扰。
2. 堆垛机控制的核心:认址、定位曲线与安全联锁
2.1 水平认址:激光测距与编码器互为冗余
堆垛机水平方向定位,这个项目用了两套传感器并联:SICK DL100激光测距仪负责绝对位置,行走轮电机尾轴上的增量编码器负责相对位置跟踪。为什么需要两套?因为实际运行中,货架和托盘的反射面非常复杂,激光测距偶尔会出现一个跳变值。如果完全依赖激光,这个跳变就可能让堆垛机停在错误货位,严重时货叉直接撞上货架。编码器长时间运行有累计误差,但短距离内非常稳定。
所以程序里的做法是:激光测距值作为主定位源,编码器脉冲数作为校验源。正常时两者差值在允许范围内,以激光值为准;一旦出现激光跳变,程序自动切换到编码器值继续定位,同时报警提示维护人员检查激光测距仪。这个逻辑其实不复杂,但避免了绝大多数认址异常。
垂直方向相对简单,提升机构采用绝对值编码器加认址片(凸轮开关)双重确认。绝对值编码器断电不丢位置,认址片用于每次经过时校正机械原点,消除链条或钢丝绳在长时间运行后的伸长误差。
2.2 定位速度曲线:先算减速点再跑
堆垛机定位的关键不是高速跑,而是怎么刹车。水平方向跑180m/min,刹车距离如果算不好,堆垛机就会冲过目标位再退回来,循环时间白白浪费。这个项目采用三段式速度曲线:加速段、匀速段、减速段,减速段末端进入低速爬行直到停止。
减速距离的计算公式是:
- 减速度a取1.5m/s²(根据载货重量和机械刚性综合设定)
- 减速距离S = V² / (2a)
- 例如3m/s降到0.15m/s的爬行速度,减速距离S = (3² - 0.15²) / (2×1.5) = 2.985m
所以程序里从目标位置反向计算减速点:当实际位置到达“目标位置减减速距离”时,变频器给定从高速切到低速。这里有个细节——减速距离不是固定值,它会随着当前速度实时变化。比如堆垛机刚启动还没达到最高速就开始减速,这时减速距离很短,用固定的减速点就会提前减速,白白浪费时间。所以程序里每个扫描周期都在计算一个“动态减速点”,只有实际位置进入动态减速点范围才切低速。
TIA Portal里用工艺对象做轴控制会更简单,MC_MoveAbsolute会自动生成运动轨迹,不需要自己算减速点。但现场项目用工艺对象时,变频器必须通过PROFIdrive报文3通讯,而且要花时间整定轴参数。我个人建议,如果你的团队对1500运动控制不熟悉,先用“变频器多段速+PLC动态减速点计算”这套传统方案,稳定第一;熟练之后再换工艺对象。
核心逻辑用SCL写大概是这样:
#rTargetPos := #TargetPos; // 目标位置,mm #rCurrentPos := #LaserDist + #EncoderOffset; // 当前位置校正 #rSpeedSet := #rMaxSpeed; // 动态减速点计算 #rDecelDist := (#rSpeedSet * #rSpeedSet) / (2.0 * #rDecel); IF (#rTargetPos - #rCurrentPos) <= #rDecelDist THEN #rSpeedSet := #rCrawlSpeed; // 进入爬行 END_IF; IF ABS(#rTargetPos - #rCurrentPos) <= #rStopWindow THEN #rSpeedSet := 0.0; // 到位停止 END_IF;2.3 货叉伸缩时序:一个状态机管到底
货叉是堆垛机上动作最频繁、也是联锁最多的机构。它的取货顺序不是简单的“伸出-缩回”两步,而是一整套状态序列。以取货为例:
- 初始状态:堆垛机水平、垂直定位完成,货叉在原位
- 货检确认:目标货位的光电检测确认无货(取货时确认有货)
- 伸叉:货叉中叉伸出到托盘下方,到位后触发伸叉到位信号
- 微升:载货台上升几毫米,让托盘脱离货架
- 回叉:货叉带回托盘缩回原位
- 货物确认:载货台上光电确认货物已随货叉回到位
- 完成:置位取货完成标志,等待下一个任务
这套状态机我用SCL的CASE语句实现,每个状态都有超时保护,任何一个动作超过设定时间没有到位信号,程序立刻停止并跳转到故障状态,防止货叉卡住后机械继续动作把货物顶翻或者撞坏货架。
货叉联锁最严格的一条:伸叉过程中,水平行走和垂直提升的使能必须被强制封锁。因为货叉在货架内部的时候,任何水平或垂直方向的移动都可能导致货叉刮蹭货架,轻则损坏传感器,重则整个堆垛机卡死。这条联锁在硬件接线和软件逻辑里都做了,程序里每个主循环都会扫描货叉状态位。
2.4 安全回路:不依赖PLC的硬件级保护
再强调一遍,堆垛机是大型运动设备,安全不能只靠PLC程序。堆垛机前后端机械限位、电气限位、硬限位撞块是三级的,电气限位接入安全继电器硬接线回路,一旦触发直接将变频器使能切断,不经过CPU。紧急停止按钮、巷道两端安全门、运行区域光栅全部走硬接线安全回路。
PLC程序里的软限位和软件联锁是第二道防线,是在硬件保护触发之前先减速、先停止。比如软限位设定在物理行程的95%位置,堆垛机越界后先减速停机,如果软限位失效继续冲,硬限位才动作。这个设计是为了减少机械冲击,毕竟硬限位撞多了,齿条和滚轮都会受伤。
安全回路的故障状态要进PLC诊断,通过HMI显示“安全回路断开”的具体区域,方便维护人员快速定位。这个看起来很基础,但很多项目前期不会注意,出故障只能现场一个一个继电器查,极其浪费时间。
3. 输送机系统的程序实现:从DTL到货物跟踪
3.1 DTL输送机的分段控制与积放逻辑
输送机系统里最常听到“DTL输送机”这个词,尤其在工创赛智能物流小车这类比赛项目里,大家习惯把动力输送线叫DTL。实际上在工业项目里,动力辊筒输送机、皮带输送机、链条输送机统称都在输送机系统里,DTL是其中一种设备形式。现场项目里,我们把整条输送线按物理位置和控制需求分成若干段,每段由一台变频器驱动,段与段之间用光电传感器做衔接判断。
为什么分段而不整线一起跑?最基本的原因是节能和安全。整条线一起转,托盘到了末端也只能等着,电机空转浪费电,而且如果某一段的托盘挡住检修通道,检修人员进出非常危险。分段控制后,每一段是否运行由“本段有没有托盘”和“下一段有没有空闲”两个条件决定,这就是积放逻辑。
积放逻辑的标准写法:
IF #Photocell_ThisSeg = TRUE // 本段光电检测到托盘 AND #Photocell_NextSeg = FALSE // 下一段光电无托盘 THEN #Motor_ThisSeg := TRUE; // 本段运行 ELSE #Motor_ThisSeg := FALSE; // 本段停止等待 END_IF;这个逻辑看起来简单,但实际项目里要处理很多边界情况。比如托盘正好压在段与段之间的传感器上,前后两个光电同时有信号,这会导致相邻两段都判断“下一段被占用”而互相等待,形成死锁。解决方法是要求两段之间留出足够的物理间距,同时程序里对“边界光电”做延迟确认,信号持续200ms才认为真的占位,防止托盘抖一下造成误判。
3.2 货物跟踪:位置不是靠猜,是靠“信息包”移动
输送机系统最核心的程序就是货物跟踪。托盘在输送线上从一个工位到另一个工位,PLC必须时刻知道每一个托盘在哪一段、它的条码是什么、它要去哪个口,否则分合流的时候就会分错。
这个项目用了“移位寄存器+数据包”的方式。在PLC里建立一组跟踪队列,每个队列元素包含:
- 托盘条码
- 货物重量(称重站结果)
- 外形检测结果(超高/超宽/超重标志)
- 目标地址
- 当前所在输送段编号
当托盘经过入库口的条码扫描枪时,PLC根据条码在WMS数据库中的配置生成一个信息包,放入入库队列。托盘每往前移动一段,光电传感器发生一次“由有到无”的变化,信息包就从队列的当前位置向后移动一位。到分合流口时,PLC检查信息包里的目标地址,决定控制换向机构往左还是往右。
这里有一个坑:光电传感器是离散的,不可能时刻知道托盘在段内的精确位置。所以程序里的做法是,把每一段再细分成几个逻辑位置(比如段首、段中、段尾),通过光电组合判断托盘处于哪个逻辑位置。逻辑位置的划分要和传感器安装位置一一对应,后面要贴标签标记清楚,不然调试的时候根本不知道信息包对应的是哪个实际位置。
3.3 输送机与堆垛机的接货联锁
输送机系统最终要跟堆垛机交互,这个接口是整个程序里联锁关系最密集的地方,也是最容易出事故的地方。堆垛机的货叉伸出来接货时,输送机如果继续送托盘,托盘会被货叉顶翻,直接砸下来。
我在这套项目里设计了6个握手信号,全部是PLC内部M区或DB位,通过PROFINET在堆垛机PLC和输送机PLC(同一个CPU,所以是内部变量)之间共享:
| 信号 | 方向 | 含义 |
|---|---|---|
| 请求交付 | 输送机→堆垛机 | 托盘已到站台,请堆垛机来接 |
| 就绪应答 | 堆垛机→输送机 | 堆垛机已定位到站台,货叉回零 |
| 开始交付 | 输送机→堆垛机 | 输送机开始将托盘送入站台 |
| 交付完成 | 输送机→堆垛机 | 托盘已完全到位,输送机停止 |
| 取货完成 | 堆垛机→输送机 | 堆垛机货叉已取走托盘 |
| 交接释放 | 双方 | 本次交接结束,输送机可进入下一托盘 |
握手顺序上,只有收到“就绪应答”后输送机才允许启动,只有收到“取货完成”后输送机才允许放行下一托盘。任何一个信号超时未到,程序都会中断操作并报警。
这套联锁看着繁琐,但正是因为这些信号环环相扣,系统才能在无人值守的情况下24小时连续运行。仓库运行期间操作人员很少进巷道,所有异常都依赖PLC自动判断和安全停机。
4. WCS调度与PLC实时交互:任务队列如何落地成动作
4.1 通信方式:S7通信直接读写DB区
立体仓库除了PLC,上层还有WMS(仓库管理系统)和WCS(仓库控制系统)。WMS管库位、管账目,WCS管设备调度决策,PLC管执行。三个层级之间怎么通信,决定了系统的实时性和可靠性。
这个项目的WCS与PLC通信,没有用中间件,也没有用OPC UA,而是直接用西门子S7通信协议,由WCS读写PLC里定义好的接口DB块。道理很简单,S7通信是西门子的原生协议,PLC作为服务端,WCS作为客户端,不需要额外部署服务器软件,不依赖第三方驱动,通信延迟在毫秒级。
OPC UA当然也可以,特别适合跨品牌设备集成、数据采集分析场景。但在这个项目里,任务上下发的频率很高,实时性要求严,而且我们不需要历史数据进上位机做大数据分析,S7直连DB是最轻量、最可靠的方案。
4.2 任务报文结构:一个DB块搞定任务下发与状态回传
任务接口DB块是整个系统最关键的“公共区域”,我把它设计成两个区域:任务下发区和状态回传区。任务下发区由WCS写入,PLC只读;状态回传区由PLC写入,WCS只读。
任务下发区数据格式:
| 偏移 | 数据类型 | 名称 | 说明 |
|---|---|---|---|
| 0 | INT | 任务号 | WCS分配的唯一编号 |
| 2 | INT | 命令类型 | 1入库 2出库 3移库 4盘点 |
| 4 | STRING[20] | 托盘条码 | 托盘唯一标识 |
| 26 | INT | 源排 | 起点货位排 |
| 28 | INT | 源列 | 起点货位列 |
| 30 | INT | 源层 | 起点货位层 |
| 32 | INT | 目标排 | 终点货位排 |
| 34 | INT | 目标列 | 终点货位列 |
| 36 | INT | 目标层 | 终点货位层 |
| 38 | BYTE | 优先级 | 0~255,数字越大越优先 |
| 40 | BYTE | 状态 | 0空闲 1已接收 2执行中 3完成 4失败 |
货位地址编码采用“排-列-层”三维坐标,每个方向占一个INT,简单直观。WCS负责把逻辑库位(比如B-03-12-05)翻译成PLC能识别的三个整数,PLC不做库位换算,只管按坐标走。边界检查写在PLC侧,任何坐标超出允许范围都算非法任务,直接拒绝并反馈错误码。
4.3 异常任务处理:取消、暂停与断电恢复
任务执行过程中,最怕的是异常。货叉没有取到货、激光跳动、变频器报警、输送机堵货,任何一个异常都可能导致当前任务失败。这个项目里,所有异常统一走一条路径:PLC置位任务状态为“失败”,写入失败原因代码,停止当前动作,然后等待WCS指令。
WCS收到失败状态后有两种处理:一是重新下发任务,让堆垛机重试;二是下发取消任务,PLC清除该任务的所有中间状态,恢复到空闲状态。这里PLC程序必须保证“取消”在任何时候都能生效,哪怕货叉已经伸出一半,也要先把货叉收回来再取消任务,不能让机构停在半空中。
断电恢复是另一个容易忽略的环节。堆垛机正在跑任务时突然断电,重新上电后PLC不知道之前跑到哪了。这个项目用掉电保持DB区保存当前任务号、目标坐标、当前状态机的Step值,上电后PLC先做一次全轴找原点动作,回到机械原点后,根据保留下来的任务状态决定继续执行还是请求WCS下发新任务。
5. 现场调试中最常踩的坑
5.1 认址跳动:激光测距数据必须先做合理性过滤
调试期间遇到最诡异的问题,是堆垛机偶尔停在距离目标货位差半个货格的位置。机械精度没问题,编码器输出也没问题,后来用Trace抓数据才发现,激光测距仪的返回值有极低概率跳变一个很大的数——比如从12000mm突然跳到12050mm,然后立刻跳回来。这个跳变刚好发生在减速阶段,PLC以为离目标很近就提前停了。
排查下来,原因是货架表面和托盘边缘在特定角度下会对激光产生多路径反射,导致测距仪内部解算出错误距离。解决办法是在程序里加“合理性滤波”:连续取3次扫描值,如果其中一次与另外两次的差值超过5mm,就丢弃这次值,用另外两次的平均值。这样处理后,再没出现过跳变引起的定位错误。
这个问题的本质是:任何传感器在真实工业环境中都不是100%可靠,PLC程序必须有容错机制。给传感器数据做合理性检查,是每一段模拟量、每一条通信数据都应该做的。
5.2 PROFINET网络丢站:一个接头浪费了一整天
网络丢站的坑看起来是网络问题,实际上往往是基础施工问题。调试过程中遇到过输送机区域某个ET200SP从站突然离线,整条输送线停机,重新扫描恢复之后运行一阵又掉线,最后把网线拆开一看,RJ45接头壳体的屏蔽层压接不良,工业现场的电柜里又有变频器这种强干扰源,信号稍弱就被干扰打断。
这件事之后,我要求所有PROFINET网线的制作必须有统一标准:屏蔽层压接必须到位,接头外壳必须锁紧,走线间距要远离变频器电缆,至少保持200mm。另外,把网络拓扑做成环网并启用MRP,这样即使某一段网线出问题,其他站点通信不受影响。
5.3 库存映射错位:托盘信息“丢包”后货位账实不符
系统运行两周后,WMS报出某货位显示有货,实际库位却是空的。库存账实不符在自动化仓库里是严重事故,必须处理。排查过程很痛苦,最终定位到入库环节:托盘经过入库输送机到达堆垛机接货站台时,有一个瞬间同时遮住了两个相邻光电,PLC检测到下落沿时把托盘的位置信息包更新到了下一个队列位置,但这时托盘其实还在原位置,导致PLC记录的位置和实际位置错开了。
解决方法是把货物跟踪的判定改成“离开确认”:只有当托盘完全离开当前光电(光电检测到由遮挡变为释放),才更新位置信息。同时在入库口增加“站台到位确认光电”,只有站台到位光电确认托盘到位,才允许堆垛机接货并登记货位。
这个坑归根结底是“状态变化”的定义不够严谨。现场的程序里,不能用“有信号”作为唯一判定,必须结合信号的前后状态和稳定时间来判断,尤其在高速输送线上,光电信号的动作时间非常短,PLC扫描周期稍慢就会漏。
5.4 货检光电误判:取货时把空货位当成有货
堆垛机去出库,到一个货位却报“货物缺失失败”,反复几次后发现,货检光电偶尔在空货位也会被遮住,原因是相邻货道的托盘在特定角度上反光,触发了对射光电的误判。这个问题的处理分两步:机械上给货检光电加装遮光罩,缩小光束照射范围;程序上增加“货位状态”与“货检光电”的并联判断——如果WMS反馈该货位有货,同时货检光电确认有货,两者一致才允许取货,否则立即停止并提示人工检查。
这个经验特别适合准备参加工创赛智能物流小车这类比赛的同学借鉴。比赛小车上光电传感器挨得近、环境光线复杂,如果没有防误判逻辑,小车反复取放货物就会疯掉。学校实验室里可以用遮挡物做测试,但现场项目的复杂程度要高得多。
5.5 双循环空跑:任务排序让堆垛机效率直接提升40%
调试后期发现一个问题:堆垛机明明很忙,出入库效率却上不去。观察一段时间后发现,WCS下发任务的顺序不合理。比如堆垛机刚在巷道最右边放完货,下一个任务让它去巷道最左边的货位取货,然后下一个任务又回到右边。路径优化没做好,堆垛机大量的时间都在巷道里来回空跑。
这个问题的本质是WCS的任务调度算法没有考虑堆垛机的当前位置和运动方向。改进方案是在WCS侧做任务预排序:优先派发与堆垛机当前位置同侧、同方向的任务;有入库任务和出库任务同时存在时,优先组合成“一出一入”的双循环任务,让堆垛机从起点出发后能完成一次“去程出库、回程入库”或者反向的完整循环。
改完排序逻辑后,单台堆垛机的出入库能力从22托/小时提升到了31托/小时,提升接近40%。稳定运行后,系统基本不再出现堆垛机空跑大半条巷道的情况。
后来我再想这个事,其实很多效率问题不是设备跑得不够快,而是调度策略让设备做了太多无用功。
如果你正在做类似的立体仓库项目,我建议你从项目的第1天就把“状态”、“联锁”、“边界”这三件事想在前面。状态定义清楚,程序不会乱;联锁设计完整,设备不会出大事故;边界检查做到位,数据不会错。堆垛机、输送机、WCS,这三大块看着复杂,拆开之后每一块都是可以独立调试的子系统,关键是接口要把牢。希望这篇分享能帮你少走一些弯路。