ST(结构化文本)是IEC 61131-3标准中功能最强、表达最接近高级语言的编程语言,广泛用于PLC控制系统中实现复杂逻辑。计数器是自动化控制中最基础也最关键的元素之一,而CTU(Count Up)、CTD(Count Down)是ST中两种标准计数器功能块。它们看似简单,但在实际工程中,因复位时机错位、使能信号抖动、计数值越界、多条件交叉触发等原因,极易引发计数失准、设备误动作甚至停机事故。本文不讲概念定义,只讲怎么写才不出错——从底层逻辑、代码实现、典型陷阱到可复用的复位策略,全部以ST语法为唯一载体,所有内容均可直接粘贴进Codesys、TIA Portal、Unity Pro等主流平台验证。
一、CTU与CTD的本质:不是“黑盒”,而是“状态机”
CTU和CTD在ST中并非内置语句(如FOR或IF),而是预定义功能块(FB),必须通过实例化调用。其核心行为由三个输入引脚和两个输出引脚共同决定:
| 引脚名 | 类型 | 作用说明 |
|---|---|---|
CU / CD |
BOOL |
上升沿触发计数动作。仅当该信号由FALSE→TRUE跳变时,计数器才响应一次。注意:持续TRUE不会重复计数。 |
R |
BOOL |
异步复位。只要R=TRUE,无论CU/CD状态如何,立即清零PV并置Q=FALSE(CTU)或Q=FALSE(CTD)。复位优先级最高。 |
LD(仅CTD) |
BOOL |
装载预设值。当LD=TRUE且CD无上升沿时,将PV强制设为PV:=PV(即保持当前值),但标准CTD不支持外部装载值;若需装载,应使用CTUD或自定义逻辑。此处以标准CTD为准。 |
PV |
INT 或 DINT |
当前计数值。初始值为0,范围取决于数据类型:INT为−32768~+32767,DINT为−2147483648~+2147483647。溢出不自动回绕,而是锁死在边界值(如INT计到32767后再+1,仍为32767)。 |
Q |
BOOL |
计数完成标志。CTU中:Q := (PV >= PV_MAX);CTD中:Q := (PV <= 0)。注意:Q为纯组合逻辑输出,无延时。 |
关键认知:CTU/CTD本身不保存“是否已触发过”的历史,它只响应边沿、只依赖当前输入电平。 所谓“计数稳定”,完全取决于你如何组织CU/CD信号的边沿生成逻辑。
二、标准CTU的ST实现:从模板到防抖
以下是最小可行、零冗余的CTU实例化代码(以DINT型为例,兼顾大计数场景):
// 声明实例与变量
ctu_inst: CTU;
reset_flag: BOOL;
count_enable: BOOL;
pulse_input: BOOL; // 外部脉冲源,例如光电开关信号
preset_value: DINT := 5; // 计满5次触发动作
// 主逻辑段(放在主程序POU或循环任务中)
// 步骤1:生成干净的上升沿脉冲(抗抖动)
pulse_edge := pulse_input AND NOT pulse_input_prev;
pulse_input_prev := pulse_input;
// 步骤2:添加软件滤波(可选,但强烈推荐)
IF filter_timer.Q THEN
filtered_pulse := pulse_edge;
ELSE
filtered_pulse := FALSE;
END_IF;
// 步骤3:启动计时器滤波(10ms去抖)
filter_timer(IN := pulse_edge, PT := T#10ms);
// 步骤4:构造最终CU信号 —— 仅当使能有效且滤波后有边沿时才计数
ctu_inst(CU := count_enable AND filtered_pulse,
R := reset_flag,
PV := preset_value);
// 步骤5:导出结果
count_value := ctu_inst.CV; // CV为当前值输出(标准CTU含CV、Q、QU等)
is_full := ctu_inst.Q; // Q为到达预设值标志
说明:
pulse_input_prev必须声明为VAR RETAIN或位于FB内部,否则每次扫描都会丢失前一状态;filter_timer是标准TP(脉冲定时器)功能块,确保毛刺宽度<10ms时不触发;count_enable是工艺允许计数的使能条件(如“电机正在运行”AND“安全门关闭”),绝不能直接用硬件信号代替;reset_flag的生成必须独立于计数逻辑,见第四节。
三、标准CTD的ST实现:避免“负计数陷阱”
CTD常被误用于“倒计时”场景(如物料剩余量提示),但其Q输出逻辑是PV <= 0,而非PV == 0。这意味着:一旦PV被意外减至−1,Q将保持TRUE直至复位——这是多数故障的根源。
正确做法:永远用CTD配合边界钳位,禁止单独使用其Q输出做关键动作。
// 声明
ctd_inst: CTD;
down_enable: BOOL;
pulse_down: BOOL;
min_limit: DINT := 0;
// 主逻辑
// 生成下降沿(用于CTD的CD输入,注意:CTD响应CD上升沿,所以需将“下降事件”转为上升脉冲)
pulse_down_edge := NOT pulse_down AND pulse_down_prev;
pulse_down_prev := pulse_down;
// 构造CD信号:仅当允许且有干净边沿时触发
ctd_inst(CD := down_enable AND pulse_down_edge,
LD := FALSE, // 标准CTD中LD无装载功能,设FALSE即可
R := reset_flag);
// 关键:手动钳位PV,防止越界
pv_raw := ctd_inst.CV;
pv_clamped := MAX(min_limit, pv_raw); // 确保不低于0
count_down_value := pv_clamped;
// 安全Q输出:仅当PV恰好等于0时置位(非<=0)
is_zero := (pv_clamped = 0);
⚠️ 警告:不要写
is_zero := ctd_inst.Q。因为ctd_inst.Q在PV=−1、−2…时恒为TRUE,失去“精确归零”意义。
四、复位策略:四种工业级可靠方案
复位不是“按个按钮清零”,而是与工艺周期强耦合的状态管理。以下是经产线验证的四大策略,按可靠性升序排列:
-
同步复位(最简,仅限调试)
reset_flag := btn_manual_reset OR (is_full AND auto_reset_en);✅ 适用:单次测试、手动模式。
❌ 风险:若auto_reset_en时序错配(如在计数中途使能),导致reset_flag与CU同拍,计数器可能漏计1次。 -
周期复位(推荐用于批次控制)
// 在每个工艺周期开始前复位 IF cycle_start_edge THEN reset_flag := TRUE; ELSIF cycle_running THEN reset_flag := FALSE; END_IF;cycle_start_edge必须是严格单脉冲(如伺服回原点完成信号的上升沿),确保每周期只复位1次。 -
条件锁定复位(高可靠性,防误触发)
// 仅当满足全部条件时才允许复位 can_reset := (is_full) AND (conveyor_stopped) AND (no_material_on_belt) AND (reset_permission = OK); reset_flag := can_reset AND reset_request; // reset_request为操作员确认信号 -
双沿复位(最高可靠,用于安全相关计数)
// 复位需“按下+松开”两个事件,杜绝粘连误动作 reset_press := btn_reset AND NOT btn_reset_prev; reset_release := NOT btn_reset AND btn_reset_prev; btn_reset_prev := btn_reset; reset_flag := (state = WAITING_FOR_RESET) AND reset_press; state := CASE state OF IDLE: IF is_full THEN WAITING_FOR_RESET ELSE IDLE END_IF; WAITING_FOR_RESET: IF reset_release THEN IDLE ELSE WAITING_FOR_RESET END_IF; END_CASE;
✅ 实践结论:在包装线计瓶、灌装线计桶、装配线计工件等场景中,第3种(条件锁定)覆盖90%需求;第4种(双沿)用于SIL2及以上安全回路。
五、CTU与CTD混合应用:CTUD的替代方案(不依赖CTUD FB)
部分老版本PLC(如早期Modicon M340)不支持CTUD功能块。此时可用CTU+CTD协同实现双向计数,并规避CTUD常见的“CU/CD同时为TRUE时行为未定义”的风险:
// 双向计数器逻辑(目标:PV ∈ [0, 100],CU加、CD减,越界钳位)
VAR
cu_pulse, cd_pulse: BOOL;
cu_active, cd_active: BOOL;
pv_work: DINT := 0;
upper_limit: DINT := 100;
lower_limit: DINT := 0;
END_VAR
// 分别处理CU和CD(互斥触发)
IF cu_pulse AND cu_active THEN
pv_work := MIN(upper_limit, pv_work + 1);
ELSIF cd_pulse AND cd_active THEN
pv_work := MAX(lower_limit, pv_work - 1);
END_IF;
// 输出
bidir_cv := pv_work;
at_upper := (pv_work = upper_limit);
at_lower := (pv_work = lower_limit);
优势:
- 完全可控,无隐式状态;
- 可加入任意前置条件(如“仅当温度>80℃时才允许加计”);
- 复位只需
pv_work := 0;,无时序依赖。
六、终极检查清单(部署前逐项核对)
| 检查项 | 合格标准 | 不合格示例 |
|---|---|---|
| 边沿生成 | 所有CU/CD信号均来自X AND NOT X_prev结构 |
直接写CU := sensor_signal; |
| 数据类型 | PV、CV、preset_value三者类型严格一致(全DINT或全INT) |
preset_value: INT; 但CV被赋给DINT变量 |
| 溢出防护 | 对CV读取后立即CLAMP(0, max, CV)或MAX/MIN处理 |
IF cv > 100 THEN start_alarm; END_IF;(未阻止cv继续增长) |
| 复位隔离 | R信号与CU/CD无共用中间变量,且R电平变化不依赖CU/CD输出 |
R := ctu_inst.Q;(Q刚置位就复位,导致计数器无法保持) |
| 初始化 | 所有_prev变量声明含:= FALSE初值 |
sensor_prev: BOOL;(冷启动时值不确定) |
最后强调:ST中没有“自动魔法”。每一个:=、每一个AND、每一个边沿,都必须是你亲手确认过的确定性动作。 把本文 checklist 打印出来,贴在编程电脑边框上——比任何教程都管用。

暂无评论,快来抢沙发吧!