简介:《Codesys中文指令手册》面向汇川AM、AC等系列中型可编程逻辑控制器(PLC),是一份系统讲解InoProShop编程软件指令体系与用法的实用资料。手册涵盖标准数据类型(BOOL、整型、浮点、字符串等)、常用指令分类及典型示例,完整覆盖比较、选择、计数器、定时器、位与字逻辑、移位、数据类型转换等指令组,既适合刚接触汇川中型PLC的初学者快速入门,也便于有经验工程师随时查阅比对。资源以PDF形式提供,共1个文件,压缩包大小约36.66MB,内容源自官方技术文档,结构清晰、引用规范。目前已有4374人学习下载,适用于运动控制、逻辑控制等场景的编程开发与调试参考,读者可按需检索指令说明,有效提升编程效率。
1. 为什么指令手册在手,现场调试还是翻车
做 PLC 调试这几年,我手里最常翻的不是梯形图模板,而是一本能随手查的 Codesys 中文指令手册。但说实话,手册查到指令只是第一步,真正让人在现场卡住的是另外三件事:这条指令是标准指令还是某个库里的扩展?参数范围到底多大?为什么照着手册写进程序,一在线就是不动作?本篇文章就顺着这三个问题拆开讲,把「查指令」变成「用指令」。
这不是写给刚入门的科普文,而是给已经碰过 Codesys、正在做设备调试或维护改造的工程师看的落地笔记。你不需要背下每条指令,但要学会怎么快速定位它、怎么验证它的执行行为、以及哪些参数边界会让程序在半夜突然罢工。适合人群:从梯形图转到结构化文本的自动化工程师、设备厂商调试人员、以及自己维护产线逻辑的非科班程序员。
2. Codesys 指令体系全景:标准 IEC 61131-3 指令与库扩展该怎么区分
2.1 指令手册的两层结构:操作符、功能块和库的关系
打开一份 Codesys 中文指令手册,第一页通常会把指令分成「操作符」和「功能块」两大类,很多人在这一步就糊涂了。我自己的理解是这样:操作符是语言层面的语法元素,你直接写在表达式里,比如a := b + c;里的+,以及选择用的SEL、MUX,它们不需要实例化,写出来就能用。功能块则必须先声明实例再调用,典型的就是TON、CTU、R_TRIG,因为每个实例内部有自己的状态变量。
这还没完。Codesys 的指令集实际上是两层叠加的:第一层是 IEC 61131-3 标准定义的指令集,包括位逻辑、比较、数学、转换、选择、定时计数这六大类;第二层才是各家库扩展,比如常用字符串处理的 Util 库、文件操作的库、运动控制相关的库。查手册时先问一句「这条指令是不是标准指令」,能省掉一半的查证时间。因为标准指令只要你的工程是用标准 IEC 语言写的,任何内核版本都能用;而库扩展和工程里引用了哪个版本的库强绑定。
区分标准指令和库扩展,我的笨办法是看工程里有没有对应的库引用。点开左侧设备树的「库管理器」,展开 Standard Libraries,如果看到这条指令在某个库的目录下,那就说明它是从库里来的,不是语言自带。很多中文手册翻译的指令条目其实是在描述标准指令,但使用者工程里装了一个很老的库版本,导致编译不过,这就是最常见的「手册和工程对不上」的根源。
2.2 同一指令的 ST 与梯形图形态如何切换与定位
还有一个容易踩的盲区:同一个指令在梯形图里是一个指令盒,在结构化文本里只是一个操作符,但两者的行为和参数完全一致。比如TON,在梯形图里你拖一个TON指令盒,接上IN、PT,从Q和ET引出线;在 ST 里就是一行tonInst(IN := bX, PT := T#2s, Q => bY, ET => etVal);。很多从梯形图转型的工程师第一次在 ST 里看到tonInst(...)会有点蒙,其实手册里的功能块参数定义已经把每个形参的输入输出方向标清楚了:IN是输入,Q是输出并用=>连接。
我一般建议新手这样加深理解:在梯形图编辑器里放一个指令盒,然后按下「视图切换」把 POU 显示方式切到 ST 视图,你会看到语言为你生成的 ST 语句。对照手册里的参数表,把这行语句读一遍,比盯着文字描述学得快。反向也一样,ST 写熟了以后,有些调试同事问「你这个 FB 里到底接到哪了」,我会临时切到梯形图看连线关系,比读代码更快。
所以查中文指令手册时,别只看一页描述,要把「指令盒长什么样」「ST 语句怎么写」「参数怎么连接」这三样同时看齐。大部分翻译版手册在这三处容易不一致,尤其是参数名的大小写和方向。遇见不确定的,以工程里插入指令时显示的声明为准,不要以手册上印的参数表为准。
2.3 判断指令库归属的三个线索
如果你担心自己查到的指令不是当前工程可用的版本,这里给你三个判断线索,是我在五六个项目上反复验证过的。第一,看指令名的前缀。标准指令都是全大写且不带库名前缀,比如ADD、MOVE,带前缀的比如SysTimeRtcGet,一看就是系统库的函数。前缀本身就是归属信息,中文手册目录里一般也会按前缀分组。第二,看指令声明里有没有VAR_IN_OUT或VAR_INPUT段。第三,也是最直接的,在工程里直接双击这条指令的名字,Codesys 会跳到它实际所在的库对象定义,那才是你工程里真正生效的版本。
3. 用最小工程把指令跑通:创建工程、声明变量、在线监视一条龙
3.1 最小工程搭建与指令验证的完整步骤
前一章把体系理清了,这一章我们建一个最小工程,把手册里的指令真正跑起来。不要一上来就搬整个项目的工艺逻辑,那样验证指令时变量多、任务周期杂,翻车了都不知道是哪一行引发的。我的习惯是:单独建一个测试工程,只放一个 PLC 程序,只声明要验证的变量,跑通了再合并回实际项目。
创建一个测试工程,设备选择上用「软 PLC」或者虚拟控制器即可,不碰真实硬件。打开 Codesys,点击「新建工程」,选择「标准工程」,设备随便选一个支持编译器版本的虚拟 PLC,POU 语言选择结构化文本(ST)。建好后先别写代码,进入设备配置,把任务周期类型选为循环,周期设置为 10ms。这里的 10ms 是验证指令时序的基准,后面判断沿输出滞后时全靠它。
接着在程序 POU 里声明以下变量,不要多:
VAR bStart : BOOL; // 模拟启动按钮 bSensor : BOOL; // 模拟传感器信号 bReady : BOOL; // 输出:准备好 bAction : BOOL; // 输出:动作使能 fnDelay : TON; // 延时接通定时器 bRise : BOOL; // 沿检测结果 trigStart : R_TRIG; // 上升沿检测实例 iCycleCnt : INT; // 扫描周期计数 END_VAR这里把功能块实例fnDelay和trigStart放在本地变量区,是最标准的做法。注意TON、R_TRIG都是功能块,不能像指令操作符那样直接写在表达式里,必须先声明实例名,再通过实例名去调用。实例名的声明风格我坚持用「前缀 + 含义」:定时器叫fnDelay,沿检测叫trigStart,这样后面交叉引用排查时一眼能看出类型。
3.2 用 ST 代码验证三类典型指令:定时器、沿检测、数学运算
接下来写主程序。这段代码我把三类最常用指令放在一个逻辑里,方便在线模式下同时观察它们之间的时序关系:
// 上升沿检测:按下 bStart 的第一循环周期,trigStart.Q 为 TRUE trigStart(CLK := bStart, Q => bRise); // 延时定时器:以沿结果作为启动信号,在输入成真后保持 1 秒 fnDelay(IN := bRise, PT := T#1s, Q => bReady, ET => fnDelay.ET); // 组合输出:传感器到来且定时完成,才置位 bAction bAction := bReady AND bSensor; // 循环计数:每个任务周期加 1,到 100 清零,用来观察扫描周期 iCycleCnt := iCycleCnt + 1; IF iCycleCnt >= 100 THEN iCycleCnt := 0; END_IF这段代码的逻辑目标是:启动按钮按下后延迟 1 秒,并且传感器已到位,动作输出才置位。trigStart(CLK := bStart, Q => bRise);这行的意思是把bStart接入CLK输入脚,上升沿时输出Q变为 TRUE 一个循环周期,并把这个结果赋给bRise。这里有一个新手常见的认知偏差:bRise不是立刻等于手动按下去的那一刻,而是在下一个任务扫描周期才被刷新为 TRUE。因为在循环扫描模型里,功能块的输出结果在当前周期末尾才会写回变量。你按一次启动按钮,要等一个扫描周期之后bRise才看到它。
fnDelay这一行更关键。PT参数接收一个TIME类型的常量T#1s,这是 Codesys 的时间字面量写法,不要写成1或者1000。Q => bReady把定时器输出连到变量上,ET => fnDelay.ET把内部计时值暴露出来,方便监视窗口实时看到计时进度。这个例子如果跑通了,你会看到:bStart按下为 TRUE → 下一周期bRise变 TRUE →T#1s内fnDelay.ET从 0ms 一路涨到 1000ms →fnDelay.Q和bReady在同一周期变为 TRUE → 若bSensor也为 TRUE,bAction即刻置位。
3.3 在线监视与断点:指令执行顺序的验证技巧
代码写好后,先点「登录」再点「启动」。很多人第一次验证指令时只停留在编译通过,没有真正进入在线监视状态,看不到变量变化,自然就不知道指令行为对不对。进入在线后,打开监视窗口,把bStart、bRise、fnDelay.ET、bReady、bAction五个变量拉进去。然后把bStart置为 TRUE,用 1~2 秒的时间观察这五个变量的刷新顺序,你就能直观看到前面说的「沿输出晚一个周期」是什么效果。
若你想验证指令更细的执行顺序,不要只看变量,要用断点。在 ST 代码行首点出断点,然后进入在线模式单步执行。断点停在trigStart那一行时,你会发现它执行后bRise并没有立刻变,得再走完一次周期才会在监视窗口里变。这又印证了扫描式执行模型的特征:指令盒的 Q 输出反映的是本次执行结束时写回的结果。
这对实际调试的意义是:如果你在梯形图里把R_TRIG的 Q 直接接到后面一串逻辑的使能位,那些逻辑在同一个扫描周期内看到的不是 TRUE,而是上一周期的旧值。所以我在多处设备上都有过「按下按钮设备死活不动作,把变量视图切到监视窗口才发现信号晚了一拍」的经历。想避坑的话,沿检测的输出先给中间变量,后面逻辑全部引用中间变量,而不是在功能块 Q 脚上直接串线。
4. 核心指令按功能分组:手册里最常查的六类指令与参数边界
4.1 位逻辑、比较与数学运算:参数不一致时最容易出隐性错
把指令分组记忆要比逐条死记快得多。IEC 61131-3 的标准指令大致就六大类,我按手册页数和现场使用频率排序,先讲位逻辑、比较和数学运算这三类。
| 分类 | 代表指令 | 典型用途 | 常见误用 |
|---|---|---|---|
| 位逻辑 | AND、OR、XOR、NOT,R_TRIG、F_TRIG | 按钮互锁、条件使能、信号沿检测 | 沿指令输出直接驱动后续逻辑,导致动作慢一拍 |
| 数学 | ADD、SUB、MUL、DIV、MOD、EXPT | 温度换算、流量累计、比例计算 | DIV 除数为零不报编译错误,运行时报错或输出异常 |
| 比较 | GT、GE、EQ、LE、LT、NE | 上下限判断、到达判定、报警阈值 | 比较两侧类型不一致,比如 INT 和 REAL 混用 |
位逻辑的EQ在 ST 里直接写成=,NE写成<>。一个高频坑是:比较指令要求两侧操作数类型严格一致。手册上会写ANY_NUM、ANY_BIT这种占位符,很容易给人「任意数值都能比」的错觉。实际上iVar > 0.5这种写法,默认严格类型检查下直接编译报错「类型不匹配」。解决办法不是去改编译器设置,而是把 0.5 改成INT_TO_REAL(iVar) > 0.5,或者把左边变量声明成 REAL。我一般把习惯定死:数值参与比较前先统一类型,绝不全靠隐式转换,因为不同版本有的隐式转、有的拒编,工程拷给别方时最容易在这里翻车。
4.2 TON/TOF/TP 与 CTU/CTD:时间、计数参数的精度边界
延时定时器在工程里可以说是出现频率最高的一组指令。TON、TOF、TP 三者名字相近,实际行为差别很大:TON 是输入为真后延时接通输出,TOF 是输入消失后延时断开输出,TP 则是不管输入怎么变化,输出只会保持固定时长。我第一次做输送线互锁时,把「停机后需要延时 5 秒才能复位」做成了 TON,现场一测试就发现逻辑完全反向,翻了车之后才老老实实把三个定时器的时序图都画了一遍。
这里要特别记住 TIME 类型的精度和范围。TIME 内部是一个 32 位无符号整数,单位是毫秒。也就是说它精确到 1ms,最大能表示到约 49.7 天。你写T#1s它存的就是 1000,写T#100ms存的是 100。开发时用 T# 字面量不会出错,但如果你是通过触摸屏写入一个「延时秒数」的整数,再手动乘 1000 转成 TIME,就要注意乘法溢出。秒数超过 4294967 就超过 TIME 上限了,实际工程里长延时场景并不少见,最短的坑是有人用整数变量直接塞给 PT 参数,忘了 TIME 不是整数,编译直接过不去。
计数器 CTU、CTD、CTUD 的参数比定时器简单一些,核心字段就是 CU/CD 时钟输入、R 复位、PV 预设值、CV 当前值。最容易出错的是 PV 和 CV 的数据类型。默认是INT,但如果你搞大型计数累计,超过INT上限 32767 会导致回绕成负数。这我在包装线计件项目上就踩过一次,计到 32767 变成负数后设备就异常停机。现在统一习惯:凡是要累计的计数器,PV、CV 全部声明成DINT或LINT,别用默认值,更别偷懒不改类型。
4.3 类型转换指令:INT_TO_REAL 的截断与四舍五入规则表
类型转换指令在中文手册里往往占十几页,看起来五花八门,其实核心就是一张表。我把工程里最常用的几组转换行为列在下面,尤其注意它们是「截断」还是「四舍五入」。
| 转换指令 | 行为 | 边界注意 |
|---|---|---|
| INT_TO_REAL | 精确转换,不丢失数值 | INT 转 REAL 在有效范围内都能无损表达 |
| REAL_TO_INT | 按舍入模式取整 | 不同编译器配置下可能是截断或四舍五入,不能想当然 |
| DINT_TO_TIME | 把整数毫秒数转成时间 | 超过 4294967295 溢出,转换前先判断范围 |
| BYTE_TO_WORD | 位扩展,高位补零 | 符号位不扩展,所以负数会变正数 |
REAL_TO_INT 这个方向是我在手册上做批注最多的一行。早期做一个加热温度控制,读上来 99.6℃,想用REAL_TO_INT转成整数去显示,结果发现它有时候是 99,有时候是 100,后来查资料和实测才知道这个转换的舍入模式受工程选项影响,不同版本的 Codesys 默认行为不一致。现在我的代码里凡是浮点转整数,都先手动加一个ROUND(),保证真正做四舍五入,再转。这个过程在中文手册里写得比较零散,我的建议是所有转换指令的舍入行为都以你当前编译器实测为准,不要轻信「转过去就是四舍五入」的贴子。测试方法很简单:建一个和第三章一样的最小工程,给一个变量赋值 99.6,转一次在线看结果,十秒钟验证完,比你记十页手册都靠谱。
5. 常见问题排查:五条把工程师卡在调试现场的血泪经验
5.1 现象:沿检测输出信号一直没有动作
有一次设备改造,按钮按下去,整条逻辑没有任何反应。在线监视看到bStart已经是 TRUE 了,但后面那串逻辑就是不动。查梯形图发现R_TRIG的 Q 脚直接被一根线拉进了后面的使能位。原因:用一个扫描周期宽的沿信号去驱动一个跨多个周期的置位逻辑,逻辑元件执行到那根线的状态是本周期之前的旧状态,信号早就过了。解决:沿检测 Q 不直接参与业务逻辑,先存进中间变量,下一周期再判断这一周期是否为 TRUE。
5.2 现象:REAL 变量相等判断永远为 FALSE
加温设备到达目标温度后,程序里写IF rTemp = 200.0 THEN停机,结果从来不停。原因:浮点数内部不是精确表示 200.0000000,可能是 199.9999998 或 200.0000001,直接比较相等必然失败。解决:用ABS(rTemp - 200.0) < 0.1这种带容差的写法。这条写进手册批注页最恰当,凡是 REAL 判断一定带死区。
5.3 现象:编译报错「类型不匹配」且不接受隐式转换
从别人项目里拷了一段逻辑,里面直接写DINT变量和INT常量相加,编译直接失败。原因:目标工程的编译选项开了严格类型检查,不是所有设备都默认允许隐式类型转换,可能你这个变量在别的控制器里是INT,在这台是DINT。解决:先看变量声明,把两边的数据类型统一,再用显式转换指令包裹被赋值的表达式。
5.4 现象:在线运行后修改程序下载,程序段全部变灰不可写
调试时为了试一个参数,直接在在线状态下点「保存修改」后重新下载,结果整个 POU 变成灰色,无法写入变量值,设备直接停了。原因:在线修改并下载会触发工程重新编译和任务停止,某些处理器和版本状态下,PLC 进入「运行中不可写」的保护态。解决:在线调试前先确认任务还有没有未保存的机器状态;下载前要预估设备会停机多久。这一条不是指令问题,但每次现场被它卡住,用户第一反应都是查手册,所以我把它列进来。
5.5 现象:功能块实例名能和全局变量名撞车
逻辑里声明了一个计数器实例叫Count,全局变量区里又有一个数据Count,编译时直接报重复定义或二义性错误。原因:全局变量和 POU 的局部变量分属不同命名空间,但 Codesys 在某些版本下解析实例名时会优先匹配库函数名,造成覆盖。解决:全局变量统一加g_前缀,FB 实例统一用小写开头加类型缩写,比如ctMain : CTU;、tonSec : TON;。这个习惯坚持一年后你回头看,会发现排查问题的速度不止快一倍。
6. 进阶:用交叉引用与强制值把指令边界钉死的两个习惯
调试到最后,真正能把指令边界摸透的,靠的不是翻手册页数,而是两个工具习惯:交叉引用和强制值。在代码里右键一个变量名,选择「交叉引用」,Codesys 会列出这个变量被哪些 POU 引用、引用的位置、以及每个引用点是读还是写。这个功能对排查「这条指令为什么拿到的输入永远是旧值」极其有用。你一眼就能看到除了你手动赋值的位置之外,是不是还有一段藏在某个功能块里的代码也往这个变量上写值。手册告诉你的只是指令怎么工作,但真正决定一条指令输入值的,往往是五六个跨 POU 的引用点,交叉引用能在三分钟内替你扫完整个工程。
第二个习惯是配合在线模式的「强制值」功能。把程序的某个输入变量强制成你要模拟的信号,比如强制bSensor := TRUE,再观察定时器和计数器的响应,这比直接在真实设备上反复拨弄传感器快得多,而且完全可重复。我调试温度模拟量滤波时,靠强制值把 PV 从 0.0 按 0.5 步进强制到 250.0,十分钟就把滤波系数边界看清了,不需要等真正的温度爬升。注意强制值是调试手段,用后必须记得解除强制,否则设备运行正常后还带着被卡的输入,又是一次半夜紧急电话。
最后一件事:拿到一份中文指令手册,别把翻译质量当成真理。指令名、参数名、方向的翻译在不同版本之间可能不一致,我在一个项目上吃过亏,手册PT参数写的「预设时间」,实际必须连接TIME类型,而工程里旧代码给它赋的是个整数。从那以后我养成了一个习惯——凡是手册和工程实测对不上的地方,我一定在批注栏里记一笔实测值,这份批注比手册本身更好用。也希望上面这些批注式的经验,能帮你把自己的那份手册越批越厚。希望帮到你。
本文还有配套的精品资源,点击获取