1. 从两个CTU说起库存差计算踩过的坑做产线库存管理或者立体仓库项目的人大概率都遇到过这样一个场景需要实时知道入了多少、出了多少、还剩多少。最直觉的做法就是拿两个计数器一个记入库一个记出库然后做减法。我最早做三菱FX系列项目的时候也是这么干的用两个CTU加计数指令一个挂在入库光电上一个挂在出库光电上程序里写一句D0 - D2 D4D4就是库存差。这个思路本身没错逻辑也清晰但实际跑起来问题就来了。第一个坑是数据一致性入库和出库如果几乎同时发生两个CTU的当前值更新有先后你读到的两个值可能不是同一时刻的快照算出来的差值会瞬间跳变。第二个坑是清零逻辑CTU计到设定值之后需要复位如果你用两个独立的复位条件很容易出现一个复位了另一个没复位的情况库存数直接算错。第三个坑最隐蔽——断电保持。CTU的当前值默认是不保持的PLC一断电两个计数器全归零库存数据直接丢失这在真实产线上是事故级别的。后来我接触到CTUD加/减计数指令才发现这个场景其实有更优雅的解法。CTUD一个指令就同时具备加计数和减计数两个输入当前值是一个寄存器加就往上走减就往下走天然就是库存这个语义。标题里说的双CTU库存差换CTUD组合本质上就是把两个独立计数器做减法这种间接方案换成一个计数器双向计数的直接方案。听起来只是换了个指令但背后的数据一致性、复位逻辑、断电保持处理方式全都变了。这篇文章我打算把这件事讲透。不管你是刚接触三菱ST语言的新手还是做了几年PLC想优化老程序的老手都能从里面找到能直接抄的东西。我会从为什么双CTU会出问题讲起然后拆CTUD的引脚逻辑和ST写法再给一套完整的库存管理程序框架最后把调试中常见的坑一个个列出来。全程用FX3U和GX Works2/3的环境来演示ST语言为主必要的梯形图逻辑也会提。2. 双CTU方案为什么会翻车三个真实故障复盘2.1 数据快照不一致导致的库存跳变先讲一个我亲身经历的故障。那是一个小型零件立库入库和出库共用一条输送线两个光电开关分别检测进和出。程序里用了C0和C1两个CTU库存值存在D100逻辑是每个扫描周期执行D100 C0当前值 - C1当前值。问题出在扫描周期和输入滤波上。入库光电触发的那一瞬间C0在当前扫描周期就更新了但C1可能因为出库光电的信号还没被读进来还是旧值。这时候D100会突然多出一个数下一个扫描周期又恢复正常。操作员在触摸屏上看到库存数闪了一下虽然时间很短但如果这个值被用来触发报警或者联动其他设备就会误动作。这个问题的根子在于两个CTU的当前值是两个独立的存储单元它们之间没有原子性保证。你没法让PLC同时读取两个计数器的值。CTUD就不一样了它只有一个当前值寄存器加和减都作用在同一个变量上读的时候天然就是一致的。2.2 复位条件分散引发的计数错乱第二个坑更常见。CTU指令有一个复位输入R当R接通时当前值归零。双CTU方案里C0的复位条件通常是入库计数达到设定值或者手动清零按钮C1的复位条件是出库计数达到设定值或者同一个清零按钮。听起来没问题对吧但实际项目中入库和出库的计数上限往往不一样。比如入库计数器设到9999就复位出库计数器设到5000就复位两个复位时机不同步。一旦某个计数器先复位库存差就会突然变成一个巨大的数比如9999减0程序如果没有做限幅处理直接就把这个值送到触摸屏或者上位机了。我见过一个更离谱的案例工程师把两个CTU的复位都接到了同一个按钮上但按钮是点动的按下的那一个扫描周期两个计数器都复位了看起来没问题。可是如果按钮接触不良只复位了其中一个另一个还保留着旧值库存数就永久性地偏了。这种故障排查起来非常痛苦因为它是偶发的。CTUD的复位逻辑就简单得多一个R输入复位就是当前值归零加和减都归零不存在只复位一半的可能。2.3 断电保持处理不当导致的数据丢失第三个坑是断电保持。三菱FX系列的CTU计数器当前值默认存在计数器软元件里而计数器软元件的断电保持特性取决于PLC的参数设置。很多新手不知道这一点以为计数器值会自动保持结果设备断电重启后库存全变成0。双CTU方案要解决这个问题你得做两件事一是把两个计数器的当前值分别传送到断电保持的D寄存器里二是在上电初始化的时候把D寄存器的值再写回计数器。这个保存-恢复的逻辑要写两遍而且如果保存的时机不对比如在计数器正在变化的扫描周期里保存保存下来的值可能是不一致的。CTUD方案只需要保存一个当前值恢复也只需要写一个值。而且CTUD的当前值可以直接指定存在断电保持的D寄存器里通过参数设置或者用DMOV指令搬运管理起来简单太多。提示三菱FX3U的计数器C0-C199是普通计数器C200-C234是断电保持计数器。如果你用CTU/CTUD指令配合D寄存器做库存建议直接用D寄存器存当前值不要依赖计数器软元件的保持特性。3. CTUD指令拆解一个指令搞定加减双向计数3.1 CTUD的引脚定义与工作逻辑CTUD是三菱PLC里的加/减计数器指令在ST语言里的调用格式和梯形图略有不同。先看梯形图里的样子CTUD指令有四个输入引脚——CU加计数输入、CD减计数输入、R复位输入、LD装载输入还有一个输出引脚QU加计数到位、QD减计数到位以及一个当前值寄存器。工作逻辑是这样的CU每来一个上升沿当前值加1CD每来一个上升沿当前值减1R接通时当前值归零LD接通时把设定值PV装载到当前值里。注意CU和CD是上升沿触发不是电平触发这一点和CTU一样。在ST语言里CTUD的调用方式取决于你用的是GX Works2还是GX Works3以及是FX系列还是Q/L系列。FX3U的ST里CTUD通常这样写CTUD_M( CU:入仓信号, CD:出仓信号, R:复位信号, LD:装载信号, PV:设定值, CV:当前值寄存器, QU加到位标志, QD减到位标志);这里CTUD_M是实例名你可以自己取。CU和CD分别接入库和出库的上升沿信号R接复位LD一般不用或者接初始化信号PV是计数上限比如9999CV是当前值存放的寄存器QU和QD是到位输出。关键点在于CV是一个寄存器加和减都改它。这就从根本上解决了双CTU方案里两个计数器不同步的问题。3.2 ST语言里CTUD的三种写法对比在实际项目里CTUD在ST中有几种不同的实现方式各有适用场景。第一种是直接调用系统指令就是上面那种写法。优点是代码简洁PLC底层优化过执行效率高。缺点是不同系列PLC的指令名和参数顺序可能不一样移植性差一点。第二种是用IF语句手写加减逻辑不依赖CTUD指令。比如IF 入仓信号上升沿 THEN 库存值 : 库存值 1; END_IF; IF 出仓信号上升沿 THEN 库存值 : 库存值 - 1; END_IF; IF 复位信号 THEN 库存值 : 0; END_IF;这种写法最灵活任何PLC都支持而且你可以很方便地加限幅、加报警、加日志。缺点是要自己处理上升沿检测代码量稍大。第三种是混合写法用CTUD指令做计数用ST做外围逻辑。比如CTUD负责加减ST负责在库存值超限时报警、在复位时记录日志。这种写法兼顾了效率和灵活性是我在项目中用得最多的。三种写法没有绝对的好坏看你的项目需求。如果只是简单的库存计数第一种就够了如果需要复杂的联动逻辑第三种更合适如果是教学或者移植性要求高的场景第二种最稳妥。3.3 CTUD相比双CTU的核心优势把CTUD和双CTU放在一起对比优势就很明显了。我整理了一个表格对比项双CTU方案CTUD方案当前值存储两个独立寄存器一个寄存器数据一致性读取时可能不同步天然一致复位逻辑需要分别复位可能遗漏一个复位信号全搞定断电保持需要保存两个值只需保存一个值代码量较多需要做减法较少直接读当前值限幅处理需要在减法后做可以在加减时做调试难度较高问题隐蔽较低逻辑直观这个表格里的每一行背后都是我在实际项目中踩过的坑。比如数据一致性那一行就是我前面讲的那个库存跳变故障复位逻辑那一行是那个按钮接触不良的案例。换成CTUD之后这些问题从根上就不存在了。4. 库存管理程序实战从框架到细节4.1 程序整体架构设计一套完整的库存管理程序不只是计数那么简单。我通常把它分成四层信号采集层、计数核心层、数据处理层、输出交互层。信号采集层负责把入库光电、出库光电、复位按钮、初始化信号这些物理输入读进来做滤波和上升沿检测。计数核心层就是CTUD或者手写加减逻辑负责维护库存当前值。数据处理层做限幅、报警判断、数据记录、断电保持。输出交互层把库存值送到触摸屏、上位机、指示灯或者联动其他设备。为什么要分这么细因为产线设备经常要改。今天加一个入库口明天改一个出库逻辑如果程序是一坨写在一起的改起来很容易出问题。分层之后你只需要改对应的那一层其他层不动。在ST里我习惯用结构体来组织这些数据。比如定义一个ST_库存管理结构体里面有当前值、设定值、报警标志、复位标志等等。这样代码可读性好后期维护也方便。4.2 信号采集与上升沿检测的ST实现上升沿检测是CTUD应用里最容易出错的地方。CTUD的CU和CD是上升沿触发但如果你直接把一个电平信号接上去PLC内部会帮你做上升沿检测。问题是如果你在ST里手写逻辑就必须自己处理上升沿。三菱ST里做上升沿检测通常用LDP指令或者自己写边沿检测逻辑。我一般用后者因为更可控(* 上升沿检测 *) 入仓上升沿 : 入仓信号 AND NOT 入仓信号_上次; 入仓信号_上次 : 入仓信号; 出仓上升沿 : 出仓信号 AND NOT 出仓信号_上次; 出仓信号_上次 : 出仓信号;这段代码的意思是如果当前信号是ON而上一个扫描周期是OFF那就是上升沿。然后把当前信号存起来供下一个扫描周期比较。这里有个细节要注意上升沿检测的变量必须是断电不保持的。如果你把入仓信号_上次存在断电保持的D寄存器里PLC重启后这个值还是断电前的状态可能导致重启后的第一个扫描周期误判上升沿。我一般用M寄存器或者不保持的D寄存器来存这些中间变量。还有一个坑是信号抖动。光电开关或者接近开关的信号可能有抖动如果抖动被当成多个上升沿库存就会多计。解决办法是在信号采集层加滤波比如连续3个扫描周期都是ON才认为是有效信号。三菱FX系列有输入滤波参数可以设置也可以在程序里自己做。4.3 CTUD核心计数逻辑的完整ST代码下面是我在一个实际项目里用的CTUD核心逻辑基于FX3U和GX Works3的ST环境。代码做了简化去掉了项目相关的具体地址你可以直接参考这个结构(* 库存管理核心逻辑 *) (* 上升沿检测 *) 入仓边沿 : 入仓信号 AND NOT 入仓信号_上次; 入仓信号_上次 : 入仓信号; 出仓边沿 : 出仓信号 AND NOT 出仓信号_上次; 出仓信号_上次 : 出仓信号; (* CTUD调用 *) CTUD_库存( CU : 入仓边沿, CD : 出仓边沿, R : 复位信号, LD : 初始化信号, PV : 库存上限, CV : 库存当前值, QU : 库存满标志, QD : 库存空标志 ); (* 限幅处理 *) IF 库存当前值 库存上限 THEN 库存当前值 : 库存上限; END_IF; IF 库存当前值 0 THEN 库存当前值 : 0; END_IF; (* 报警判断 *) 库存高报警 : 库存当前值 库存高报警设定; 库存低报警 : 库存当前值 库存低报警设定; (* 断电保持 *) 库存保持值 : 库存当前值;这段代码里有几个关键点。第一库存当前值我用的是D寄存器并且设置了断电保持这样PLC重启后库存值不会丢。第二限幅处理放在CTUD调用之后防止因为信号异常导致库存值超出合理范围。第三报警判断用的是设定值不是硬编码的数字方便在触摸屏上修改。LD引脚我接了初始化信号这个信号在上电后的第一个扫描周期接通把PV的值装载到CV里。但这里有个问题如果PV是库存上限那上电后库存值就变成上限了这显然不对。所以实际项目中LD要么不接要么接一个专门的装载值而不是上限值。我一般不用LD而是用复位信号加手动设定来处理初始化。4.4 断电保持与数据恢复的处理技巧断电保持是库存管理里最容易被忽视、但出问题最严重的地方。我见过太多项目调试的时候一切正常一断电重启库存就归零然后操作员手动改库存改着改着就乱了。三菱FX3U的D寄存器D0-D199是普通寄存器D200-D511是断电保持寄存器具体范围取决于PLC型号和参数设置。我一般把库存当前值放在D200以后的区域确保断电保持。但光放在保持区还不够。因为CTUD的CV如果直接指定D200PLC在运行过程中会不断改写D200断电时D200的值会被保存。这看起来没问题但如果你在程序里还做了库存保持值 : 库存当前值这种操作就要注意两个寄存器的关系。我的做法是CTUD的CV直接指定断电保持的D寄存器比如D200。然后在上电初始化的时候判断D200的值是否在合理范围内0到库存上限之间如果不在就复位到0或者一个安全值。这样既保证了断电保持又防止了异常数据导致的问题。(* 上电初始化 *) IF 首次扫描标志 THEN IF 库存当前值 库存上限 OR 库存当前值 0 THEN 库存当前值 : 0; END_IF; 首次扫描标志 : FALSE; END_IF;首次扫描标志可以用M8002FX系列的初始脉冲来触发或者自己用一个不保持的M寄存器做标志位。注意断电保持寄存器的值在PLC下载程序时可能会被清除具体行为取决于GX Works的设置。下载前记得备份库存数据或者用触摸屏做数据恢复功能。5. 调试与排查那些文档里不会写的坑5.1 库存值跳变的排查思路库存值跳变是最常见的故障现象。表现是触摸屏上的库存数偶尔闪一下或者突然多一个少一个。排查的时候我一般按这个顺序来第一步确认是信号问题还是逻辑问题。在ST里加一个临时变量记录每次入仓边沿和出仓边沿触发时的库存值然后在线监控。如果边沿触发的次数和实际货物数量对不上那就是信号问题如果边沿次数对但库存值不对那就是逻辑问题。第二步如果是信号问题检查光电开关的安装位置和滤波参数。光电开关离货物太近或者太远都可能导致误触发。FX3U的输入滤波可以在PLC参数里设置默认是10ms可以适当加大。第三步如果是逻辑问题重点检查上升沿检测和CTUD的调用顺序。CTUD的CU和CD如果同时接通PLC的处理顺序会影响结果。三菱的CTUD指令如果CU和CD同时有上升沿当前值不变加1再减1。但如果你手写逻辑就要注意IF语句的顺序。5.2 CTUD计数不准确的常见原因CTUD计数不准除了信号抖动还有几个原因。一个是扫描周期过长导致上升沿被漏掉。FX3U的扫描周期一般在几毫秒到几十毫秒如果程序写得太大扫描周期超过信号的最小间隔就会漏计数。解决办法是优化程序把不重要的逻辑放到低速扫描或者中断里。另一个是CTUD的PV设置不当。PV是计数上限当当前值达到PV时QU输出ON。如果你把PV设成9999但实际库存最多只有100那QU永远不会ON库存满标志就失效了。反过来如果PV设得太小库存还没满QU就ON了也会误报警。还有一个隐蔽的原因是复位信号和计数信号同时到来。如果复位信号和入仓边沿在同一个扫描周期接通CTUD的行为取决于指令内部的优先级。三菱的CTUD复位优先于计数所以当前值会归零而不是加1。如果你的逻辑依赖这个顺序就要特别注意。5.3 常见问题速查表我把这些年遇到的CTUD相关问题整理成了一个速查表方便你排查现象可能原因排查方法解决方案库存值偶尔跳变信号抖动或扫描周期不一致监控边沿触发次数加输入滤波优化程序库存值一直为0断电保持未设置或复位信号常通检查D寄存器范围和复位逻辑设置断电保持修正复位条件库存值只加不减CD信号未接通或出仓边沿检测错误监控出仓边沿变量检查出仓信号和边沿逻辑库存值超出上限限幅逻辑缺失或PV设置过大检查限幅代码加限幅修正PV复位后库存值不为0复位信号未真正接通或CV地址错误监控复位信号和CV值检查复位逻辑和地址断电重启后库存丢失D寄存器未设保持或初始化逻辑错误检查PLC参数和初始化代码设置保持区修正初始化这个表里的每一行都是我在实际项目中真实遇到过的。比如库存值只加不减那一行有一次是因为出仓光电的线接错了信号根本没进PLC排查了半天才发现是硬件问题。所以软件排查之前先确认硬件信号是正常的。5.4 从双CTU迁移到CTUD的注意事项如果你手里有一个已经用双CTU跑着的项目想迁移到CTUD有几个地方要注意。第一地址映射要改。双CTU方案里库存值是C0当前值 - C1当前值这个值可能没有单独的寄存器。迁移到CTUD后库存值就是CTUD的CV寄存器所有引用库存值的地方都要改成这个新地址。第二复位逻辑要合并。双CTU有两个复位条件迁移后要合并成一个。合并的时候要想清楚原来的两个复位条件是不是等价的如果一个复位条件是入库计数到上限另一个是出库计数到上限合并后应该用哪个我的建议是复位条件只保留手动清零和初始化计数上限用报警来处理不要用复位。第三断电保持要重新设置。双CTU方案可能保存了两个计数器的值迁移后只需要保存一个。但要注意原来的保存逻辑如果还在可能会覆盖新的CV值。迁移时把旧的保存逻辑删掉或者屏蔽掉。第四测试要覆盖边界情况。迁移完成后重点测试这几个场景同时入仓和出仓、库存满时继续入仓、库存空时继续出仓、断电重启、复位按钮按下。这些边界情况是双CTU方案容易出问题的地方迁移后要确认CTUD方案都处理正确了。6. 进阶玩法CTUD在复杂库存场景下的扩展6.1 多仓位库存管理的CTUD组合单个CTUD只能管一个库存值。如果产线有多个仓位比如A仓位、B仓位、C仓位每个仓位独立进出那就需要多个CTUD实例。这时候代码组织就很重要了。我的做法是定义一个结构体数组每个仓位一个元素TYPE ST_仓位 : STRUCT 入仓信号 : BOOL; 出仓信号 : BOOL; 入仓边沿 : BOOL; 出仓边沿 : BOOL; 入仓上次 : BOOL; 出仓上次 : BOOL; 当前值 : DINT; 上限 : DINT; 复位 : BOOL; 满标志 : BOOL; 空标志 : BOOL; END_STRUCT END_TYPE然后定义一个数组仓位 : ARRAY[1..3] OF ST_仓位;用FOR循环来遍历处理。这样加仓位只需要改数组大小逻辑不用动。但要注意CTUD指令在ST里调用的时候每个实例需要独立的背景数据块或者独立的CV寄存器。如果你用FOR循环调用CTUDCV的地址要动态计算这在三菱ST里稍微麻烦一点。我的做法是手写加减逻辑不用CTUD指令这样在FOR循环里处理起来更灵活。6.2 库存数据的上报与可视化库存值算出来之后通常要送到触摸屏或者上位机。三菱的触摸屏GOT系列可以直接读D寄存器所以库存值放在D寄存器里最方便。如果上位机是组态软件或者自己写的程序可以通过以太网或者串口读D寄存器。我一般会做几个额外的寄存器库存当前值、库存上限、库存高报警设定、库存低报警设定、库存满标志、库存空标志。这些值都放在连续的D寄存器里触摸屏或者上位机一次性读取效率高。如果要做库存趋势图可以定时把库存值存到一个数组里比如每分钟存一次存24小时的数据。三菱FX3U的D寄存器数量有限存不了太多数据可以用文件寄存器或者扩展存储器。或者直接把数据送到上位机由上位机来做趋势和报表。6.3 CTUD与其他指令的联动应用CTUD不只能做库存。任何需要双向计数的场景都可以用比如位置控制正转加计数反转减计数当前值就是位置流量累计正向流量加反向流量减当前值就是净流量人数统计进门加出门减当前值就是当前人数设备运行时间启动加停止减或者用两个CTU分别计运行和停止在这些场景里CTUD的优势和库存管理是一样的一个寄存器搞定双向计数数据一致性好复位逻辑简单。我最近做的一个项目是用CTUD做电梯的楼层位置。电梯上行时CU接通下行时CD接通当前值就是当前楼层。比用两个CTU做减法简洁多了而且不会出现楼层显示跳变的问题。7. 我个人的几点实操体会CTUD这个指令看起来简单但真正用好需要理解它的工作逻辑和边界情况。我刚开始用的时候以为把CU和CD接上就行了结果发现复位逻辑、断电保持、上升沿检测这些细节才是决定程序稳定性的关键。双CTU方案不是不能用在简单场景下它也能工作。但如果你的项目对数据一致性要求高或者需要断电保持或者后期可能要扩展CTUD方案会省掉很多麻烦。迁移的成本也不高主要是改地址和合并复位逻辑半天时间就能搞定。最后分享一个小技巧在调试CTUD的时候我会在程序里加一个手动加减的功能通过触摸屏的按钮直接控制CU和CD这样不用实际触发光电开关就能测试计数逻辑。这个功能在排查问题时特别有用你可以精确控制加几次、减几次然后看库存值对不对。调试完成后把这个功能屏蔽掉就行不影响正常运行。另外库存值的显示建议加一位小数或者做单位换算比如实际库存是1234个但触摸屏上显示1234操作员看起来更直观。如果库存值可能很大用DINT或者REAL类型不要用INT防止溢出。三菱FX3U的INT是16位最大32767库存超过这个数就会溢出变成负数这个坑我踩过。