一条皮带线十几个料箱同时在线跑入口不停上料出口分了三条支线每箱该去哪还得去哪不能跟错不能堵料。这种项目我接过不少说白了就是给输送线装上目的地跟踪的能力。标题这个项目我完整交付过一遍设备布局图、电气图纸、博途程序全套都齐程序用西门子TIA Portal写核心就是编码器测距加队列跟踪。这篇把整个项目的思路、图纸要点、程序算法和现场调试的坑都捋一遍做输送线集成的工程师或者正在研究目的地跟踪怎么落地的兄弟可以拿去做参考。1. 需求拆解与技术选型输送线为什么需要记住每个料箱的目的地1.1 这条线的工艺描述与控制任务先还原一下现场工艺。一条主传动带长度大概15米入口在上游由人工或机械手把料箱放到皮带上下游并排分布着3个分流口支线每个支线对应不同的工位或缓存区。料箱从入口进入后系统根据上位机或HMI输入的指令给每个料箱指定一个目的地1号、2号或3号支线。料箱随着皮带往前走走到对应分流口时挡板或者气缸要把它推进支线其他料箱则放行。控制任务拆开看就是三件事第一识别每个料箱该去哪第二实时知道每个料箱在皮带上的位置第三在正确的位置、正确的时间执行分流动作。第二件事就是跟踪的核心——不是让料箱跑过去撞上挡板才想起该转向而是系统从它上线那一刻起就盯着它走提前计算到达分流口的时间时机对了就动作。这项目跟普通传送带的启停控制完全是两码事。普通皮带线只要管哪段转、哪段停、堵了没而这项目要管的是每个料箱个体在哪。难度不在逻辑多复杂而在思维要转换过来你面对的不是一根皮带而是皮带上的一支车队。1.2 分段启停与连续跟踪两种控制思路的取舍最朴素的做法是分段式把主线分成几段每段独立电机驱动料箱走到某段末尾光电检测到就停段、等分流。好处是逻辑简单不依赖编码器坏处是节拍慢、料箱间距大、堵料概率高而且所有料箱必须按先进先出的顺序依次处理根本无法实现后面的料箱去3号口、前面的去1号口这种乱序分流。送一个料箱到3号支线中间还要经过1号、2号支线的入口。如果靠分段停、逐段判断机械上要多做很多动作处理速度被卡得死死的。所以这项目最终选了连续跟踪方案皮带不停机编码器测速测距料箱在线上的位移由程序实时计算。我选用的是编码器队列跟踪的组合。编码器装在皮带从动辊轴上皮带每走一段距离编码器输出对应数量的脉冲程序里维护一个跟踪队列每个料箱上线时入队把自己的目的地和入线时的编码器值记下来每扫描周期计算当前编码器值-入线编码器值换算成实际的位移一旦位移达到该目的地分流口的距离设定值就触发分流。选型时最纠结的是要不要上视觉或RFID。说实话如果预算充足RFID加读写器是最无脑的方案每个料箱贴标签、到点读卡逻辑简单到小学生都能写。但这个项目预算有限而且分流口距离是固定的料箱在皮带上是单列规则排列不存在超车交错的情况编码器累计测距完全够用。这也是所有输送线项目选型时都要先问自己的问题工艺允不允许用便宜的方案解决问题。2. 设备布局与电气设计图纸阶段就决定跟踪精度的细节2.1 设备布局图传感器位置是跟踪精度的第一道保障这套系统的精度不靠程序靠的是传感器布局。图纸上第一件事是把上料光电的位置定死它既要检测有料箱进入又要作为料箱入线的位置零点。我的习惯是把它放在皮带起始点下游大概0.5米处——不能放在最头端因为料箱刚放上去时速度还没跟上皮带在头端触发容易误判。每个分流口的正前方要有分流检测光电这是触发推料动作的信号源。注意必须是提前量光电装在分流口上游约一个料箱长度的位置料箱前端触发光电时料箱中心线离分流口推杆还有半个料箱身位推杆推出才能正好推到料箱中部。这个距离怎么算后面调试章节单独说。分流口下游再装一个分流到位光电确认料箱真的离开主皮带了。同时主线末端必须有末端保护光电——任何料箱跑到这里都说明之前某次分流失败了程序要报警停机绝不能允许料箱从皮带末端掉下去。设备布局图里还必须标注编码器安装位置。有经验的师傅都知道编码器不能装在驱动辊上因为驱动辊和皮带之间可能有打滑测出来的长度会和实际位移对不上。装在从动辊上也只能测辊子转了多少圈皮带本身的打滑依然测不到。所以布局图上要标出跟踪参考点的概念——所有分流口的距离尺寸都必须从同一个参考点开始测量通常就是上料光电的位置。2.2 电气系统与I/O规划从CPU选型到高速计数接线CPU选了西门子S7-1200系列1214C DC/DC/DC理由很简单项目规模不大1214C自带6个高速计数器通道编码器直接接就行不需要额外的高速计数模块。如果分流口再多、跟踪队列更长可以考虑S7-1500但中小型线体1214C足够。I/O规划表先列清楚不要让接线师傅来现场发挥信号类型点位说明地址规划DI上料光电I0.0DI分流口1检测光电I0.1DI分流口2检测光电I0.2DI分流口3检测光电I0.3DI分流口1到位光电I0.4DI分流口2到位光电I0.5DI分流口3到位光电I0.6DI末端保护光电I0.7DI急停I1.0DI启动/停止按钮I1.1/I1.2DQ1号分流气缸电磁阀Q0.0DQ2号分流气缸电磁阀Q0.1DQ3号分流气缸电磁阀Q0.2DQ警灯三色Q0.3~Q0.5AI预留变频器反馈可不接高速计数编码器A相HSC1I0.0复用注意与普通DI分开这里有个坑我必须提醒S7-1200的普通DI和高速计数是共用的比如I0.0可以作为普通输入也可以作为HSC1的A相输入。所以编码器接高速计数通道后上料光电就不能再接那个物理点了。1214C上HSC可以用的点通常是I0.0/I0.1、I0.3/I0.4等组合规划I/O时先查好手册再分配地址不然图纸画完发现光电点位和高数点位冲突整个布局都要返工。编码器我用的增量式500脉冲/转推挽输出A/B两相接HSC的A相和B相电源24V直供屏蔽层在PLC侧单端接地。选500线的理由后面计算里会看到匹配皮带线速度后脉冲频率很从容不会触发CPU的计数上限。2.3 编码器测量与速度换算一套具体的计算实例到了这步其实是纯数学。从动辊直径120mm周长算3.14×120376.8mm约0.377m/转。编码器500脉冲/转那么皮带每走1米产生的脉冲数就是500÷0.377≈1326脉冲/m。线速度按0.4m/s运行编码器输出频率就是0.4×1326≈530Hz离S7-1200高速计数器上限100kHz差了两个数量级稳得很。每个扫描周期设为10ms皮带前进0.4×0.014mm对应脉冲增量约5.3个脉冲。这个数决定了跟踪分辨率每个脉冲约0.754mm对于一个800mm长的料箱、分流口间距几米规模的系统来说完全够用。软件里我建立了一个皮带位置LinePos变量单位mm不是直接用原始脉冲数而是每个周期读取高速计数器当前值和上一周期的值做差乘以0.754得到毫米增量累加到LinePos上。这样做的好处是LinePos是一个只增不减的LReal值不用担心编码器32位计数器的溢出问题程序逻辑清爽。后面避坑章节我会详细说这个差的处理。3. 博途程序核心目的地跟踪算法的数据结构与SCL实现3.1 队列方案选型移位寄存器与大结构数组程序设计的核心就是跟踪队列怎么组织。我见过两种主流做法各有适用场景。第一种是移位寄存器法在DB里开一个数组每个元素代表皮带上一个固定位置的槽位。皮带每前进一个固定距离比如500mm程序就把整个数组往一个方向移一位新进入的料箱写入数组头部。这方法思路直观但有几个硬伤数组长度决定了跟踪容量槽位固定导致料箱间距必须是槽距的整数倍否则一个料箱可能横跨两个槽跟踪就乱了。第二种是我最终采用的是大结构数组法数组的每个元素独立描述一个料箱不用管它的物理位置和其他料箱的相对关系。每个元素包含是否占用、目的地编号、入线时的LinePos值。皮带上同时能跑多少个料箱取决于料箱长度和最小安全间距。按800mm料箱和200mm最小间距算15米线最多能在线约15个料箱数组开20个元素留余量。结构数组法的最大优势是灵活料箱可以乱序比如3号口堵了后进来的料箱可以先派去2号口某个料箱被人工拿走直接把它那个元素清空就行不用重排整个队列。缺点是程序要用FOR循环遍历数组每次扫描周期把所有占用槽位扫一遍。20个元素的循环PLC跑起来毫无压力。3.2 DB块设计跟踪队列、目的地参数表、状态字博途程序里我开了一个全局DB名字叫TrackDB里面分三块跟踪队列、目的地参数表、运行状态字。跟踪队列就是一个20元素的数组每个元素是UDT类型。UDT字段如下TYPE UDT_TrackItem VERSION 0.1 STRUCT Active : Bool; // 该槽位是否有料箱占用 DestID : Int; // 目的地编号 1/2/3 EntryPos : Real; // 入线时LinePos值单位mm NeedDecel : Bool; // 是否已完成分流前的减速判断预留 RecheckCount : Int; // 光电确认计数器调试用后面讲 END_STRUCT END_TYPE目的地参数表是一组Real常量存的是每个分流口相对上料光电的距离值。这些数值是从布局图上量出来的写在DB里而不是程序里硬编码是为了现场调试方便皮带打滑严重或分流口位置微调时直接在HMI上改DB值就能重新标定不用动程序重新编译。我把这个经验分享给很多人了非常实用。状态字用一个Int变量每位表示不同状态第0位皮带运行中、第1位有料箱待分流、第2位堵料报警、第3位跟踪队列满。这些状态位供HMI显示和报警逻辑使用。3.3 SCL核心代码注册、遍历、到达判断、分流触发程序逻辑我放在FC块里在每个OB1扫描周期调用。SCL代码拆成几段看每段都很短但合起来就是完整闭环。注册逻辑上料光电I0.0上升沿触发时把新料箱的信息写入队列第一个空槽位。目的地的来源是HMI设定的当前批次目的地或者上位机通过PROFINET写到PLC的一个Int变量里。现场如果没有上位机最简单的方式就是HMI上三个按钮操作员根据订单按对应的目的地按键程序内部记下这个值并保持到下一个上升沿。// 上料光电上升沿注册新料箱 IF TrackDB.InputRising AND NOT TrackDB.InputLast THEN // 找一个空槽位 FOR i : 0 TO 19 DO IF NOT TrackDB.Queue[i].Active THEN TrackDB.Queue[i].Active : TRUE; TrackDB.Queue[i].DestID : TrackDB.CurrentDestID; // 当前设定的目的地 TrackDB.Queue[i].EntryPos : TrackDB.LinePos; // 记录入线位置 EXIT; END_IF; END_FOR; END_IF; TrackDB.InputLast : TrackDB.InputRising;遍历更新逻辑每个扫描周期检查队列中所有Active槽位计算当前位置增量判断是否达到目的地分流口位置。这里用了一个关键变量CurDestIdx来记录当前槽位的目的地因为同一时刻可能有多个料箱都到了分流口附近比如队列里第3个槽位的料箱想去2号口它还没到第5个槽位的料箱已经到2号口了。遍历时必须按顺序逐个判断不能想当然认为最早的槽位先动作——如果前一个槽位的料箱目的地是靠后的口它可能在后一个料箱还没接近分流口时就被放行了这没问题但如果调度要求也是乱序的遍历逻辑必须保证每个料箱独立判断互不干扰。// 遍历队列检查是否到达目的地 FOR i : 0 TO 19 DO IF TrackDB.Queue[i].Active THEN // 该料箱走了多远 pos : TrackDB.LinePos - TrackDB.Queue[i].EntryPos; destID : TrackDB.Queue[i].DestID; // 判断是否到达对应分流口的触发位置 CASE destID OF 1: triggerPos : TrackDB.DestPos[1]; 2: triggerPos : TrackDB.DestPos[2]; 3: triggerPos : TrackDB.DestPos[3]; END_CASE; // 到达触发位置且该分流口允许推料没有正在推其他料箱 IF pos triggerPos AND NOT TrackDB.Pushing[destID] THEN TrackDB.PushCmd[destID] : TRUE; // 触发分流 TrackDB.Queue[i].Active : FALSE; // 该槽位任务完成 END_IF; END_IF; END_FOR;上面是核心逻辑的第一版但实际在博途里我分了两个扫描周期来处理一是FIFO式的先进先出位置判断二是独立判断流量控制的版本。用FIFO版本时如果1号口堵了所有想去1号口的料箱都会在1号口的触发位置停下来等后面的料箱也全部被挡住程序就会堵死。所以最终版本改成了位置独立判断分流口占用标志位某一时刻3号口也可以同时有多个料箱排队等着推料只要气缸没在动作中就可以连续触发。实际应用下来这个改动的效果立竿见影。推料执行PushCmd不是直接把气缸电磁阀置位而是进一个FIFO的推料命令缓存由一个独立的推料FB依次处理。原因很简单气缸一次只能推一个料箱如果两个料箱几乎同时到达3号口两个PushCmd同时置位第一个推完第二个就没法再推了。推料FB内部用定时器控制电磁阀得电时间推杆伸出0.5秒缩回然后清除占用标志位再去执行下一条命令。3.4 扫描周期与高速计数器的配合用增量累计避免溢出高速计数器的原始值要转换成LinePos这步处理不好会在长时间运行时出各种怪问题。直接拿HSC原始值和某个固定引用值做差在PLC连续运行几个月后可能出问题——32位有符号计数器一旦溢出回绕差值会变成巨大的负数料箱瞬间就瞬移了。我用的做法是高频周期做增量累计// 每扫描周期读取HSC原始值DInt curValue : HSC1.Count; delta : curValue - lastValue; // 检查是否发生计数器回绕通常不会但做了保险 IF delta 1000000 THEN delta : 0; // 异常跳变丢弃当周期数据 END_IF; lastValue : curValue; // 脉冲增量换算成毫米累加到线体位置 TrackDB.LinePos : TrackDB.LinePos DINT_TO_REAL(delta) * 0.754;乘法在博途里要注意运算顺序0.754改成毫米/脉冲系数最好先在DB里做成Real常量避免每次扫描都做大数乘法。另外LinePos是Real类型运行一年之后累加到几十亿毫米也就是几千公里Real的精度在毫米级可能会丢所以满一个月或到达一定值后在HMI上做个校准归零即可。停机维护时清零是严格写在操作手册里的。4. 现场调试与验收图纸和程序之外的坑4.1 编码器方向与噪声先解决信号再谈算法编码器装上去第一件事不是调程序是看方向对不对。皮带正转时HSC计数值是增还是减如果程序按增量算而编码器接反了LinePos会倒着走料箱还没到分流口位置就被判定为已经过了后面的逻辑全乱。方向不对有两种解决一是对调A/B相接线从物理上改方向二是程序里乘一个方向系数。我习惯接线按图纸固定好方向把方向修正放在程序里用一个DirFactor变量等于1或-1调机时直接在监控表里改不用动线。还有一个常见问题变频器启动瞬间或继电器动作时编码器信号线上会串入干扰脉冲导致LinePos无故多跳几毫米。解决手段是给HSC配置数字滤波S7-1200的高速计数器可以设置输入滤波器把滤波时间设到0.2ms以上对付500Hz的脉冲信号频段的干扰已经足够。4.2 皮带打滑与位置漂移光电校准的必要性调试时最怕的不是程序bug而是皮带和辊子之间打滑。启动瞬间、料箱较多负载大的时候驱动辊和皮带之间会滑动编码器装在从动辊上时也能感知到部分打滑但皮带负载变化导致的实际伸长和回弹从动辊也测不准确。料箱实际走了5米编码器以为走了5.2米误差累积起来推料时机越来越偏最后料箱被推杆撞到而不是正好推正。解决思路是粗定位精校正。粗定位用编码器实时估算位置精校正靠分流口前的检测光电。具体做法每个分流口检测光电亮起时说明有一个料箱到了但程序不知道它是队列里的哪一个。好在皮带上料箱不会超车队列顺序和实际物理顺序一致所以可以让第一个Active槽位和光电信号匹配把该槽位的EntryPos强制更新为当前LinePos值相当于重新清零重记。代码量只有几行但效果非常明显// 分流口检测光电上升沿时修正队列中第一个Active槽位的位置 IF TrackDB.PhotoRising[1] AND NOT TrackDB.PhotoLast[1] THEN FOR i : 0 TO 19 DO IF TrackDB.Queue[i].Active THEN TrackDB.Queue[i].EntryPos : TrackDB.LinePos; EXIT; END_IF; END_FOR; END_IF;这个修正机制必须在每个分流口都装检测光电的前提下才有效。如果你的项目为了省成本只在分流口装了挡住位置的光电没有这个确认光电那皮带打滑问题迟早会变成现场投诉。4.3 推料时间窗从气缸动作时间反推触发时机分流动作的时机计算是很多人搞不明白的。推料气缸是单杆气缸从得电到推杆完全伸出约0.4秒。料箱运行速度0.4m/s如果等到料箱中心正好对着推杆时才推推杆伸出来时料箱已经跑过去0.16米了只好推个尾巴。所以要做提前量。我的计算方法是料箱长800mm分流检测光电装在分流口上游400mm处。当料箱前端触发光电时料箱中心距离分流口推杆还有400400800mm这一段是光电到推杆的距离料箱半长。以0.4m/s运行800mm需要2秒才到中心。而气缸伸出只要0.4秒所以可以等料箱再往前走1.6米左右再推。但这时候等待时间太长推杆推出时料箱中心正好到位推杆伸出的过程中正在接触料箱的中部可以稳定推入支线。具体到程序就是检测光电上升沿后延时1.4秒再置位推料电磁阀电磁阀保持0.8秒确保推杆完全伸出复位完成。延时值我做成HMI可调变量推料延时和推料保持时间因为实际气缸的响应速度、料箱重量不同延时必然要微调。这里给个参考表参数初值调优范围说明推料延时1.4s1.0~2.0s光电触发到推杆得电的时间推料保持0.8s0.5~1.2s电磁阀得电保持时间保证料箱完全脱离主皮带推料间隔1.5s1.2~2.0s同一分流口两次推料之间的最小间隔防止与前箱尾撞现场调机方法很直接放一个料箱让它去3号口观察推杆推出瞬间推在料箱的哪个部位。推在正中最好偏前了把延时调大偏后了把延时调小。一次调定之后稳定性很高。4.4 堵料检测与异常恢复停机重启后队列怎么处理堵料是输送线项目必须处理好的场景。我的做法每个分流口设一个分流完成超时——检测光电触发后5秒内分流到位光电没有亮起认为该料箱没有被成功推出可能是气缸没动、料箱卡在推杆处、或者推歪了立即报警停机。同时末端保护光电一旦亮起直接报警停机防止料箱掉出皮带。停机之后最棘手的问题是队列怎么恢复。如果料箱都还好好地在皮带上重启后编码器不会自己记住当前的LinePos因为PLC断电后Real变量要么保持保持型DB要么清零非保持型。我的处理方法是DB里的LinePos设成非保持型每次上电从0开始然后把所有Active槽位清空让现场操作员手动确认皮带上哪些位置有料箱用HMI上的手动添加按钮重新逐个登记料箱目的地。虽然听起来笨但比尝试自动恢复要可靠得多——自动恢复的逻辑很容易在混合料箱、被人工干预过的场景下出错造成后续一连串错流。还要考虑人工拿走料箱的情况。现场工人看到某个料箱有问题直接从皮带上拿走了队列里这个槽位还是Active状态后面的位置修正会一直和它错位。因此每个分流口检测光电上升沿时程序还应该记录当前队列里第一个Active槽位的料箱ID如果连续两个光电上升沿之间某个槽位一直没被匹配上就自动把它标记为丢失并清除。这属于锦上添花的逻辑但做完这步程序的容错能力会明显上一个档次。5. 项目交付与版本兼容比程序本身更影响口碑的事5.1 交付物清单布局图、电气图纸、博途程序怎么组织项目交付时不光要交程序还要交一整套技术文件。很多做项目的朋友程序写得很好但交底资料一团乱设备出问题时自己都得翻半天才能找到对应图纸这就是败笔了。我这套项目的交付清单是这样的设备布局图CAD或PDF标清皮带线长度、各分流口间距、传感器位置、编码器安装点、操作员站位置。图上所有的距离尺寸必须和程序里DB中的目的地位置参数一一对应出图后要做一遍核对这是我吃过亏才养成的习惯。电气原理图主回路、控制回路、PLC接线图、端子图、I/O分配表、材料清单。分册装订如果有改线电子版和纸质版必须同步更新只要有一次拖延后面就会积累一堆不一致。博途项目工程源程序、库文件、HMI画面程序。提交前要把程序做一次在线/离线一致性检查确保你交付的是真正在运行的那一版。操作手册与调试记录包括HMI按钮说明、故障报警代码表、参数设定表、现场调机的记录和最终参数。文件命名方式也很重要。我建议按项目编号-文件类型-版本号-日期规则比如LS-2019-柜内接线图-V2.1-20230331.dwg。版本号必须和图纸改动一一对应否则现场改了图纸没改版本号谁都分不清新旧版本。5.2 博途版本与程序移植的兼容性提醒博途这两年版本更新很快V15、V16、V17、V21接踵而至不少兄弟在打开别人发的项目时遇到过版本不兼容的弹窗。我的程序是用TIA Portal V16写的拿到V15里根本打不开只能通过项目升级向导转成高版本而且升级后可能有些指令库和显示参量要做小调整。如果你遇到的是V20、V21版本打开V16老项目时需要注意访问等级不能修改这类问题这通常不是程序本身的问题而是项目保护级别与高版本默认安全策略的冲突。版本移植这块我的经验和建议是在做项目时尽量别用最新版本除非客户设备强制要求。最新版往往有刚发现的稳定性问题而且做配套的库文件、第三方GSD文件不一定跟得上。一个稳妥项目优先用经过一年多验证的成熟版本比如V16或V17。交付时把程序源文件压缩成zip附带一个版本说明.txt写清项目所用的软件版本、硬件固件版本和升级注意事项。这一个小文档能省掉大量售后电话。再往深里说S7-1200和S7-1500的固件版本也会影响高速计数模块的行为。同一个HSC配置块在固件版本4.0和4.3的设备上表现就有微妙差异。调试时如果感觉高速计数响应不对劲第一步应该是查看CPU固件版本是否和程序创建时的版本一致而不是怀疑代码逻辑。这是我调了好几台设备才总结出的规律。最后再分享一个实用小技巧博途里做这类跟踪算法程序仿真调试时要格外注意高速计数器在仿真中的表现。TIA Portal的PLCSIM对高速计数器的模拟支持是打了折扣的如果你的程序依赖HSC实时值仿真时经常出现计数不动的现象。我的做法是做一个 仿真模式切换位在仿真时用内部定时器按固定周期给LinePos加一个模拟增量HSC逻辑绕过在线时再切回真实编码器模式。这样程序逻辑可以在办公室里先跑通大部分用例到现场只需要调参数省掉了大量现场排错时间。