干工控这一行,尤其是刚摸到汇川PLC的人,十个里有八个第一次打开INPROSHOP,不是被运动控制难住的,而是被最不起眼的位逻辑搞得晕头转向。常开、常闭谁都认识,但一到现场接线、写程序、联调,才发现“认识符号”和“搞懂逻辑”完全是两码事。更不用说上升沿和下降沿,很多干了两三年的人,也只停留在“用过”的层面,真问他沿指令到底是怎么实现的、为什么有时候信号明明到了却没触发,他答不上来。
这篇文章就专门把INPROSHOP里位逻辑的四大指令掰开揉碎讲清楚。适合刚入门汇川PLC编程的小白,也适合那些被现场怪问题折磨过、想回头补基础的项目工程师。内容不只是讲指令符号,而是把物理接线、扫描周期、故障安全、在线调试这些串成一条完整的链路。看完你不仅能写对程序,还能明白为什么这样写才可靠。
1. 定位位逻辑:为什么这四个指令是所有PLC程序的地基
1.1 从一次“按下按钮没反应”的排查说起
先讲个真事。有个朋友调试一台小型设备,用的汇川H5U,INPROSHOP编程。现场反馈说“急停按下去了,设备还在动”,他第一反应是急停回路接错了。结果拿万用表一通量,急停按钮的常闭触点输入信号在按下瞬间确实从ON变成了OFF,PLC输入指示灯也灭了。但程序里就是没停,电机还在跟着跑。查了大半天,最后发现他在梯形图里写的急停逻辑是:
|/| 急停输入 ( 电机运行 )也就是用了常闭指令去读这个信号。输入信号在正常状态下是ON,程序里常闭指令取反后反而不通;真按了急停,输入变OFF,常闭指令反而导通。方向整个反了。
这件事听起来像个段子,但在实际项目里一点不罕见。原因就是很多教材只告诉你“常开是NO、常闭是NC”,却没说清楚:程序里的常开/常闭指令,和你接的那个物理按钮的常开/常闭触点,是两个维度的东西。两者叠加之后的最终效果,才是你真正输入到逻辑里的电平状态。
1.2 INPROSHOP在汇川平台里的位置
在讲指令之前,得先把软件平台的关系理一理。汇川现在主要有几条PLC产品线:早期的H1U/H2U小型机用的AutoShop,AM系列、AC800系列、H5U、EASY系列用的INPROSHOP。INPROSHOP是基于Codesys V3内核二次开发的,所以它继承了IEC 61131-3的编程体系,支持LD梯形图、FBD功能块图、ST结构化文本、SFC顺序功能图、CFC连续功能图。
这个背景很重要,因为很多人在AutoShop和INPROSHOP之间横跳的时候,会发现同一个逻辑,两个软件的“长相”不太一样,甚至指令名字都有差异。比如AutoShop里的上升沿可能叫PLS/PLF,而INPROSHOP里则是标准的R_TRIG/F_TRIG,或者触点属性里的“上升沿”选项。理解了它基于Codesys,你很多指令的查找和使用习惯就能很快对齐。
位逻辑在INPROSHOP里对应的就是逻辑运算这一大类,核心就是常开触点、常闭触点、上升沿、下降沿这四个。听起来简单,但几乎所有复杂控制——电机启停、报警连锁、模式切换、单次触发计数——都是从这四个指令的组合开始的。
1.3 四大指令的一句话语义与符号总览
先把最基础的定义用表格列清楚,后面每一节再逐个深挖。
| 指令 | 梯形图符号 | ST等价写法 | 导通条件 | 典型用途 |
|---|---|---|---|---|
| 常开触点 | --| |-- | 变量直接用 | 变量为TRUE时导通 | 读取启动按钮、传感器信号、运行状态 |
| 常闭触点 | --|/|-- | NOT 变量 | 变量为FALSE时导通 | 读取停止按钮、故障信号、互锁条件 |
| 上升沿 | --|P|-- | R_TRIG实例 | 变量从FALSE变为TRUE的瞬间导通一个周期 | 按钮单次触发、计数、模式切换 |
| 下降沿 | --|N|-- | F_TRIG实例 | 变量从TRUE变为FALSE的瞬间导通一个周期 | 停止信号捕捉、物料到位后离开检测、故障恢复计数 |
这个表不用背,但建议收藏。实际写程序时你至少要知道自己在找“读取一个高电平有效信号”还是“读取一个低电平有效信号”,这样才能选对指令。
2. 常开与常闭:接线、扫描和“故障安全”的完整链路
2.1 程序里的常开/常闭和物理触点到底什么关系
很多人学PLC第一步就卡在“常开常闭”上,因为这里有两套概念叠加在了一起。
第一套是物理层:按钮、继电器、传感器输出,它们的触点有常开(NO)和常闭(NC)之分。常开触点平时断开,动作时闭合;常闭触点平时闭合,动作时断开。
第二套是程序层:梯形图里的常开指令和常闭指令,是针对PLC输入映像区里的布尔变量来做判断的。常开指令:变量为1,则导通;常闭指令:变量为0,则导通。
关键来了——物理触点通过输入电路转换成PLC内部的变量时,并没有一个“自动取反”的过程。你接的是物理常开触点,那么不动的时候,输入变量就是0;接物理常闭触点,那么不动的时候,输入变量就是1。至于程序里用常开还是常闭指令去读这个变量,完全取决于你想让这个信号在什么条件下“有效”。
用大白话讲:物理触点决定的是“外部电路的通断状态”,程序指令决定的是“内部逻辑对状态的解释”。两套东西自由组合,会产生四种情况。实际工程里最常用的组合,反而是一堆人搞不懂的“物理常闭 + 程序常开”。
2.2 为什么急停按钮必须用常闭触点接、常开指令读
这是工控人绕不开的经典问题:急停按钮为什么要接常闭触点?为什么程序里反而用常开指令(| |)去读它?
先看物理层。急停按钮用常闭触点串联在24V回路上,正常状态下回路是通的,PLC输入点是ON。一旦拍下急停,触点断开,输入点变成OFF。如果发生断线、接线松脱、24V电源掉电这些故障,输入点同样会变成OFF。
这就体现了“故障安全”的核心思想:用断电表示异常,让任何故障都导向安全侧。如果急停用常开触点,那么正常状态下输入是OFF,拍下急停输入才变成ON。万一急停那根线断了,PLC收不到任何信号,它无法区分“没按急停”和“急停线断了”,设备就可能带着安全隐患继续运行。而用常闭触点,一旦断线,PLC看到的是和“拍下急停”一样的OFF状态,逻辑自然进入停机保护。
程序层为什么用常开指令读?因为PLC内部变量在正常状态下是ON(1),你想表达的是“急停正常、允许运行”这个条件成立,所以用常开指令,变量为1时导通。按下急停后变量变0,常开指令断开,运行条件不满足,设备停机。
这里用表格整理一下这个经典组合的效果:
| 外部状态 | 物理常闭触点 | PLC输入变量 | 程序用常开指令 | 逻辑结果 |
|---|---|---|---|---|
| 正常运行 | 闭合 | 1 | 导通 | 急停条件满足 |
| 急停按下 | 断开 | 0 | 断开 | 急停触发 |
| 线路断线 | 无法闭合 | 0 | 断开 | 急停触发 |
你只需要记住一个工程结论:带安全功能的停止回路,外部接常闭,程序用常开指令读。这样写,断线等于急停,永远朝向安全方向。
2.3 断线检测:一个让很多新手意外的细节
顺着上面这个思路再延伸一步,就涉及断线检测。用常闭触点接急停还有一个好处——系统上电后,如果按钮回路断线,PLC输入点根本不会亮,操作员或调试人员一眼就能看出异常。如果用了常开触点,断线后输入点也是灭的,但你和“没按按钮”的正常状态完全区分不开,只能靠程序里的逻辑去猜。
在实际项目里,我还习惯给急停回路增加一个“回路监视”的BOOL变量,用来判断输入信号是否在“正常电平”。比如急停输入正常情况下必须是ON,如果程序刚下载完或者设备上电后,这个信号不是ON,我会直接在触屏上弹一条“急停回路异常”的报警。这样断线、接线错、IO模块没上电,都能第一时间暴露。
这种“以正常电平状态作为有效性判据”的思路,就是位逻辑在工程中非常典型的应用——通过一个简单的常开触点读取,完成远比“通断判断”复杂的安全监控功能。
2.4 INPROSHOP中的指令插入与ST写法
在INPROSHOP的梯形图编辑环境里,插入常开和常闭触点很简单。LD编辑界面下,工具栏里直接选“常开触点”或“常闭触点”,也可以在空白处右键选择插入。需要注意:触点上的变量名必须是BOOL类型。很多人在这里踩坑,把INT变量拖进去,编译直接报类型错误。
ST语言下,常开就是直接引用变量名,常闭就是前面加NOT。比如一个典型的电机启动自锁逻辑,用ST写是:
IF xStart AND NOT xStop THEN xRun := TRUE; END_IF注意这里有个陷阱:ST里xStart AND NOT xStop和梯形图的“启动按钮常开串接停止按钮常闭”是等价的。如果你在ST里直接把停止按钮信号写成xStop,那停止按钮的状态就得反过来理解——这恰恰是很多从梯形图转ST的人频繁犯错的地方。我的经验是:不管用哪种语言,程序注释里明确写出“物理常开/常闭”和“逻辑有效电平”,防止自己过两天就忘了当初的意图。
3. 上升沿与下降沿:扫描周期里的“瞬间”如何被捕捉
3.1 沿检测的底层原理:上一周期状态对比
常开常闭很好理解,就是看当前状态。上升到下降沿则完全不同——它们看的是“状态的变化过程”,而不是“状态本身”。
PLC程序是周期性扫描执行的,一个扫描周期里依次完成读输入、执行程序、刷新输出。对PLC来说,外部信号是一个一个周期地“采样”进来的。沿指令做的事情,就是把这个周期的输入状态和上一个周期的输入状态做一个比较。
上升沿的触发条件:上一周期是0,这一周期是1。 下降沿的触发条件:上一周期是1,这一周期是0。
这里的关键在于:PLC必须记住“上一周期”的状态。这就是为什么在IEC 61131-3体系里,R_TRIG和F_TRIG不是普通的触点指令,而是带内部状态的功能块。它们内部隐藏了一个变量,专门用来存储上一次调用的输入状态。所以你在INPROSHOP里插入上升沿指令时,系统会要求你给这个功能块分配一个实例名,不能像常开触点那样直接扔一个变量上去。
这个设计不是汇川特有的,Codesys体系都这样。理解了这个内部机制,你就能明白后面要讲的很多坑为什么存在。
3.2 R_TRIG与F_TRIG在INPROSHOP里的实例化细节
在INPROSHOP梯形图里插入上升沿指令,最常见的方式是:在功能区找到“R_TRIG”功能块,拖到编辑区,然后它会显示一个类似这样的结构:
R_TRIG CLK := xButton Q => xPulseCLK是输入信号,Q是输出。你需要在程序里先声明一个R_TRIG类型的变量,比如rTrigButton,然后调用它。
ST写法更直观:
rTrigButton(CLK := xButton, Q => xPulse); IF xPulse THEN iCount := iCount + 1; END_IF注意几点:
实例必须全局唯一。同一个R_TRIG实例不要在多处调用,否则它的内部状态会被交叉修改,逻辑会变得不可预测。这个后面专门讲。
Q输出不要直接当普通变量去写。Q是一个功能块输出,它在这一周期产生一个扫描周期的脉冲,然后立刻失效。你要用的通常是这个脉冲去触发一个动作,而不是把它当作持续状态保持。
PLC首次扫描时,沿指令的初始状态。R_TRIG默认的“上一周期状态”是FALSE,F_TRIG默认的“上一周期状态”是TRUE。也就是说,如果程序下载后第一个扫描周期输入就是高电平,R_TRIG会误触发一次。这一点在设备上电、程序热下载时要特别小心,后面翻车现场再展开。
3.3 沿信号的时效性:过短的脉冲为什么会被漏掉
很多人以为只要信号跳到1了,上升沿就一定能捕捉到。实际上没这么简单。PLC对输入的采样不是连续的,而是在每个扫描周期的输入刷新阶段,把输入端子上的状态锁存进输入映像区。也就是说,一个外部脉冲如果宽度太短,短到在两次扫描之间就消失了,PLC根本不知道它来过。
举个例子:假如PLC扫描周期是5ms,外部有一个2ms的短脉冲。如果这个脉冲恰好在两次扫描之间出现,那么PLC看到的是“上一周期0、这一周期也0”,上升沿永远不会触发。
这种问题在实际项目里最常见的场景就是高速计数信号、旋转编码器的Z相脉冲、位置传感器的瞬时信号。处理办法一般是:要么把信号接到支持中断或高速计数的输入通道,要么在外部加硬件滤波和展宽电路,要么把扫描周期缩短。靠沿指令去抓过短的脉冲,是抓不住的。沿指令能抓到的是“持续时间大于一个扫描周期”的状态变化。
另外还有一种情况:信号本身不短,但它的变化发生在程序执行到某条指令之前,却被输入刷新“等”了过去。这也是为什么有些工程师在处理快速信号时,会专门把这段逻辑放到一个独立任务里,或者调整输入滤波时间。INPROSHOP支持多任务配置,如果你对某个输入信号的实时性有要求,最好单独确认一下这个IO映射和任务周期。
3.4 “沿指令能滤波器抖动”是最大误解
新手最容易产生的错误认知:按钮有抖动,用上升沿指令就能只触发一次。真相是,沿指令和抖动没什么关系。
按钮按下的时候,机械触点会在几毫秒到几十毫秒内反复通断。这个过程在PLC眼里就是一连串的0、1、0、1。如果你在程序里用上升沿去捕捉按钮信号,PLC很可能会在抖动期间捕捉到多个上升沿,导致一次按键变成了三次计数。
沿指令保证的是“一个扫描周期内只导通一次”的特性,但它无法区分“这次上升沿是用户真正按下按钮”还是“触点抖动造成的电平跳跃”。要消除抖动,得在信号进入逻辑之前做软件滤波——最常见的就是用TON延时,或者写一个简单的“连续N个扫描周期状态一致才认为有效”的判断。
实际项目里我一般这样做:
// 按钮滤波:持续5ms为高才认为真正按下 tonFilter(IN := xButtonRaw, PT := T#5MS, Q => xButtonDebounced); rTrigFilter(CLK := xButtonDebounced, Q => xButtonEdge); IF xButtonEdge THEN iPressCount := iPressCount + 1; END_IF先滤波,再取沿。这个组合才是可靠的单次触发方案。别把沿指令当滤波器用。
4. 真实项目中最常见的位逻辑翻车现场与排查链路
4.1 翻车现场一:常闭信号接进来,指令方向写反了
这就是开头那个急停案例的完整版。现象:急停按下,设备不停。排查过程可以给各位一个标准参考。
第一步,看PLC输入点。用INPROSHOP在线监视功能,直接观察急停输入对应的BOOL变量。急停按下时,变量如果从1变0,说明外部接线没问题,输入信号到了PLC。
第二步,看程序里这个变量是怎么被使用的。把程序切到在线状态,用“交叉引用”功能找出所有用到这个变量的位置。检查每个位置是常开还是常闭指令。
第三步,检查逻辑是否反了。如果外部接的是常闭触点,程序里又用常闭指令读取,那就是双重取反,逻辑方向和按钮的实际动作正好相反。修正方式要么改指令方向,要么换物理触点,但推荐改程序,因为安全回路外部接线有硬件标准,不能乱改。
这个排查链路看起来很简单,但现场调试时往往因为旁边有人催、脑子一乱,就会跳过前两步直接改程序。我自己的习惯是:凡是涉及安全信号的位逻辑,在线监视确认电平变化永远排在改程序前面。
4.2 翻车现场二:程序下载后沿指令立刻误触发一次
有个设备用的汇川EASY系列PLC,每次用INPROSHOP重新下载程序后,气缸都会莫名其妙动作一次。查了很久发现是某个R_TRIG的问题。
原因就是前面提到的初始状态:R_TRIG的内部状态寄存器默认是FALSE。程序下载完成后,如果输入信号恰好在第一个扫描周期就是TRUE(比如某个传感器上电后常亮),那么第一拍就会被判断为“上升沿”,触发动作。
解决办法有几个:
一是给沿功能块做初始化,确保上电/下载后内部状态等于当前输入状态。但R_TRIG的功能块实例不方便直接强制内部变量,所以更常用的方法是:在程序开头做初始化,把一个初始恒FALSE的信号先喂给R_TRIG跑一个周期,再切入真实信号。比如:
// 上电后第一个周期,先让xEnable为FALSE,把沿功能块的状态“校准”到OFF xIn := xSystemReady AND xRawSignal; rTrigCalib(CLK := xIn, Q => xPulse);这么做有点绕,我的经验是:在设备启动初始化段里,显式地把需要捕捉沿的信号先用FALSE赋值跑一次沿指令,或者干脆用脉冲信号只在特定使能条件下才接入逻辑。这个方法不复杂,但能避免很多热下载后的偶发误动作。
4.3 翻车现场三:同一个沿实例在多处调用引发的灵异现象
INPROSHOP里同一个功能块实例允许在多个位置调用,但你要清楚,功能块的内部状态是所有调用位置共享的。这就会引出一个非常隐蔽的bug。
假设你在程序多个地方写了:
rTrigA(CLK := xSignal, Q => xPulse_1); // 位置1 rTrigA(CLK := xSignal, Q => xPulse_2); // 位置2PLC扫描是从上到下执行的。执行位置1时,rTrigA看到“上一周期0、这一周期1”,把内部状态更新为1,Q输出1。执行位置2时,rTrigA看到“上一周期1、这一周期1”,Q输出0。最后xPulse_1得到一次脉冲,xPulse_2什么也得不到。
也就是说,同一个实例被多个地方调用,第二个调用点永远拿不到想要的沿脉冲。更危险的是,如果两个调用点的输入信号还不一样,内部状态会被第一处调用更新,第二处调用的结果就完全看扫描顺序了。
正确做法是:一个沿功能块实例只服务一个逻辑点。如果多处都需要对同一个信号取沿,就声明多个实例。这也是我在评审新同事程序时重点盯的条款。
4.4 翻车现场四:用沿指令做按钮计数,一按蹦三下
按钮计数是最常见的沿应用,但也最容易出问题。表面现象是:按一次按钮,计数器跳了好几下。
第一步基本就能锁死原因:抖动。如果外部输入信号没做滤波,PLC会在按钮抖动的那几毫秒里识别出多个上升沿。对PLC来说,这些上升沿都是“合法的”,因为每个上升沿之间可能隔了一个或几个扫描周期。
解决方案就在3.4节,先滤波再取沿。如果你对响应速度有要求,比如按键时间原本就只有几十毫秒,就需要调小滤波时间,或者用输入模块自带的数字滤波参数。汇川IO模块一般都有输入滤波设置,默认值可能偏大,比如10ms,某些高速场合要手动调整。
这里还想补充一种情况:计数乱跳也可能不是按钮抖动,而是你的计数器变量类型溢出了。用INT计数,超过32767就乱跳;用UINT,超过65535也乱跳。按钮计数这种场合,习惯性用DINT甚至LREAL更稳妥。别看这个细节小,现场排查半天最后发现是溢出的情况我见过不止一次。
5. 一个能直接抄作业的完整示例:启停、急停与计数组合逻辑
5.1 需求与IO分配
综合前面聊的所有知识点,我设计一个非常典型的小控制逻辑,你直接照着写就能跑通。
需求:一台小型设备,有一个启动按钮、一个停止按钮、一个急停按钮、一个计数传感器。要求:
- 按下启动按钮,设备运行(有运行输出)
- 按下停止按钮,设备停止
- 拍下急停按钮,设备立即停止,并且逻辑上不允许再次启动,直到急停复位后重新按启动
- 传感器每检测到一个工件,计数加1
- 如果传感器信号抖动,不能在计数上出现多计
IO分配:
| 变量名 | 类型 | 对应物理信号 | 外部接线 |
|---|---|---|---|
| xEStopIn | BOOL | 急停按钮 | 常闭触点接24V |
| xStartIn | BOOL | 启动按钮 | 常开触点接24V |
| xStopIn | BOOL | 停止按钮 | 常闭触点接24V |
| xSensorIn | BOOL | 工件检测传感器 | NPN/PNP根据模块类型 |
| xRunOut | BOOL | 运行输出 | 驱动中间继电器 |
| xFaultLamp | BOOL | 急停报警灯 | 驱动指示灯 |
这里要特别说一下停止按钮的接线。工业现场最常见的停止按钮,也是接常闭触点,逻辑上用常开指令读。原理和急停一样:正常情况下停止信号是ON,按下停止信号变OFF,一旦断线系统默认停止。
5.2 梯形图思路拆解与ST对照实现
这套逻辑,我用ST写一遍,每一段都加上注释,方便你对照梯形图理解。
PROGRAM Main VAR xEStopIn : BOOL; xStartIn : BOOL; xStopIn : BOOL; xSensorIn : BOOL; xRunOut : BOOL; xFaultLamp : BOOL; xSensorOk : BOOL; xSensorEdge: BOOL; tonSensor : TON; rTrigSensor: R_TRIG; iCount : DINT; xRunReady : BOOL; END_VAR逻辑主体:
// 1. 急停回路,物理常闭,程序用常开指令读 // 正常时xEStopIn为TRUE,表示急停回路正常 xFaultLamp := NOT xEStopIn; xRunReady := xEStopIn; // 2. 启动/停止/自锁 // 启动按钮用沿触发更安全,避免按住按钮期间重复执行 rTrigStart(CLK := xStartIn AND xRunReady, Q => xStartEdge); rTrigStop(CLK := NOT xStopIn AND xRunReady, Q => xStopEdge); IF xStartEdge THEN xRunOut := TRUE; END_IF IF xStopEdge OR xFaultLamp THEN xRunOut := FALSE; END_IF // 3. 传感器信号滤波 + 上升沿计数 tonSensor(IN := xSensorIn, PT := T#5MS, Q => xSensorOk); rTrigSensor(CLK := xSensorOk, Q => xSensorEdge); IF xSensorEdge THEN iCount := iCount + 1; END_IF几处设计说明:
启动停止都用沿触发。因为启动和停止按钮都是瞬动型的,用沿触发保证“按下一次只执行一次”,不会因为程序扫描快、按钮按得久造成状态反复翻转。
停止逻辑用常闭触点解读。外部xStopIn常闭接法,正常为TRUE,取反后为FALSE,所以rTrigStop的CLK在正常时是FALSE,不会误触发;按下停止按钮,xStopIn变FALSE,取反后变TRUE,产生沿。
急停复位后的重启问题。一旦急停触发,xFaultLamp为TRUE,xRunOut被强制为FALSE。此时即使急停复位,xRunOut也不会自动变成TRUE,必须重新按启动按钮。这就是“故障复位后手动再启动”的安全要求。
传感器先滤波再取沿。如果传感器本身输出很干净,你可以把TON的PT设小一些,甚至去掉滤波直接用R_TRIG。但实际项目里,光电传感器对工件反光、毛刺边缘很敏感,不滤波很容易多计。
5.3 在线调试与强制操作的注意事项
写完之后,你用INPROSHOP连上PLC,在线调试时建议按这个顺序验证:
先强制急停输入为TRUE,确认xFaultLamp为FALSE、xRunReady为TRUE、xRunOut能正常置位。然后把急停输入强制为FALSE,确认xRunOut立即变FALSE,xFaultLamp变TRUE。
再把启动按钮的输入强制为TRUE一个扫描周期再恢复FALSE,确认xRunOut变TRUE并保持。恢复启动按钮输入为FALSE后,xRunOut应当保持TRUE,这验证了自锁逻辑。
最后强制停止按钮输入为FALSE一瞬再恢复TRUE,确认xRunOut能停止。
这里特别提醒一个强制操作的风险:强制输入的变量时,一定要记住复位所有强制值再退出现场。我就见过有人在调试时强制了启动按钮输入为TRUE,忘了取消强制,结果设备一上电不管有没有人按按钮,设备自己就启动了。INPROSHOP里强制功能很直观,但也有配套的“全部取消强制”操作,调试完务必执行一遍。
5.4 从位逻辑到功能封装的下一步
当你把这套启停急停计数逻辑跑通之后,就可以考虑把它封装成一个功能块了。INPROSHOP支持用户自定义功能块,把xStartIn、xStopIn、xEStopIn、xSensorIn作为输入,把xRunOut、xFaultLamp、iCount作为输出。封装好之后,同类设备直接拿来复用,不仅节约编程时间,也可以保证不同项目里的安全逻辑行为一致。
我就是这样干的:把常见的“带安全回路的启停逻辑”做成了一个标准库功能块,后面所有出图的项目都调用它。省下来的时间,都用来处理真正头疼的问题了。
最后再说一点个人体会。位逻辑看起来简单,但它决定了整个设备的安全底线和操作体验。越是看起来基础的东西,越值得花时间把它彻底想明白。尤其是常闭信号和沿指令的初始状态这两个细节,几乎每个项目里都会碰到,解决了这些,你的程序稳定性和调试效率都会上一个台阶。