4、指令表(IL)编程:IL语法、操作符、程序示例、与汇编语言的对比
指令表(Instruction List,IL)是IEC 61131-3标准中定义的五种编程语言之一,属于文本化编程语言。它源于欧洲PLC厂商(如西门子STL、ABB指令表)的长期实践,以类似汇编语言的助记符形式描述控制逻辑。IL语言的核心价值在于:它提供了一种与硬件指令集高度对应、执行顺序明确、调试直观的编程方式,特别适合逻辑密集、时序敏感、对执行效率有要求的控制任务。在IEC 61131-3的框架下,IL被定义为一种“低级”语言,但通过标准化的操作符和修饰符,它依然具备结构化编程的能力。本章将深入解析IL的语法体系、操作符语义、程序组织方式,并通过与汇编语言的对比,帮助工程师理解IL的底层逻辑与适用边界。
上图展示了IL语言的核心结构、操作符分类、执行模型以及与汇编语言的对比维度。IL语言的本质是一种“面向控制逻辑的汇编”,它通过隐式的累加器(CR,Current Result)串联起一系列操作,使得每一行指令都简洁且执行顺序明确。下面我们将从语法细节、操作符语义、程序示例和对比分析四个维度展开深入解读。
4.1 IL语法体系:从指令行到程序组织单元
4.1.1 核心原理:指令行的构成与累加器机制
IL程序的基本单元是指令行,每一行由四个可选字段组成:标签(Label)、操作符(Operator)、修饰符(Modifier)和操作数(Operand),行尾可附加注释。其语法格式如下:
[标签:] 操作符 [修饰符] [操作数] (* 注释 *)
示例:
START: LD %IX0.0 (* 读取输入端子0.0 *)
ANDN %IX0.1 (* 与输入0.1的取反值相与 *)
ST %QX0.0 (* 输出到端子0.0 *)
IL语言最独特的机制是累加器(CR)。CR是一个隐式的、全局的中间结果存储单元。每条指令执行时,会读取CR的当前值作为第一个操作数(对于双目运算),与指令中显式给出的操作数进行运算,结果再写回CR。例如,AND %IX0.1的含义是:将CR的当前值与输入%IX0.1进行逻辑与运算,结果存入CR。这种设计使得IL指令行非常紧凑,不需要像高级语言那样显式写出“result = a AND b”。
4.1.2 操作流程:IL程序的扫描与执行
IL程序的执行遵循严格的顺序扫描规则:
- 初始化:程序启动时,CR被清零,所有变量保持初始值。
- 顺序执行:从程序的第一条指令开始,逐行执行,直到遇到
RET(返回)指令或程序结束。 - 括号处理:当遇到左括号
(时,当前CR值被压入内部堆栈,括号内的表达式独立计算,遇到右括号)时,堆栈中的值弹出并与括号内结果进行运算。 - 跳转与调用:
JMP指令改变执行顺序,CAL指令调用子程序,RET指令返回调用点。
这种执行模型与PLC的循环扫描机制(输入采样→程序执行→输出刷新)天然契合,每一行指令都在一个扫描周期内完成。
4.1.3 案例拆解:一个简单的启保停电路
以经典的电机启保停控制为例,输入为启动按钮Start_PB(常开)、停止按钮Stop_PB(常闭),输出为接触器Motor_Run。IL程序如下:
LD Start_PB (* 加载启动按钮状态 *)
OR Motor_Run (* 与当前输出状态相或,实现自锁 *)
ANDN Stop_PB (* 与停止按钮的取反相与,实现停止优先 *)
ST Motor_Run (* 结果输出到接触器 *)
执行过程分析:
- 第一行:CR = Start_PB的值。
- 第二行:CR = CR OR Motor_Run,即如果启动按钮按下或电机已在运行,CR为TRUE。
- 第三行:CR = CR AND (NOT Stop_PB),由于停止按钮是常闭触点,按下时Stop_PB为FALSE,取反后为TRUE,因此CR保持;未按下时Stop_PB为TRUE,取反后为FALSE,CR被清零,电机停止。
- 第四行:将CR值赋给Motor_Run。
这个案例展示了IL如何用四行指令实现一个完整的逻辑控制,且每一行的物理意义清晰,便于现场调试。
4.1.4 常见误区与总结
误区1:混淆修饰符N的位置。 ANDN表示“与操作数的取反值相与”,而NAND在IL中不是标准操作符。务必记住,N是修饰符,紧跟操作符之后,作用于操作数,而非操作符本身。
误区2:忽略CR的隐式传递。 初学者常试图在每行都显式写出结果变量,这违背了IL的设计哲学。IL的CR是隐式的,过度显式化反而降低代码可读性。
误区3:括号使用不当。 括号必须成对出现,且嵌套深度受编译器限制。复杂的括号嵌套会显著降低可读性,建议拆分为多个中间变量。
经验提醒:在编写IL程序时,建议每行只写一个操作,并善用注释。虽然IL允许在一行内写多个操作(用分号分隔),但为了可维护性,坚持“一行一操作”原则。
4.2 IL操作符详解:从逻辑到控制流
4.2.1 核心原理:操作符的分类与语义
IEC 61131-3标准定义了IL的完整操作符集,按功能可分为四大类:加载/存储类、逻辑运算类、算术运算类和控制流类。下表列出了常用操作符及其语义:
| 类别 | 操作符 | 语义 | 示例 |
|---|---|---|---|
| 加载/存储 | LD | 将操作数加载到CR | LD %IX0.0 |
| ST | 将CR存储到操作数 | ST %QX0.0 | |
| S | 将操作数置位(CR为TRUE时) | S Motor_Run | |
| R | 将操作数复位(CR为TRUE时) | R Motor_Run | |
| 逻辑运算 | AND | CR := CR AND 操作数 | AND %IX0.1 |
| OR | CR := CR OR 操作数 | OR %IX0.2 | |
| XOR | CR := CR XOR 操作数 | XOR %IX0.3 | |
| NOT | CR := NOT CR(单目) | NOT | |
| 算术运算 | ADD | CR := CR + 操作数 | ADD 5 |
| SUB | CR := CR - 操作数 | SUB 3 | |
| MUL | CR := CR * 操作数 | MUL 2 | |
| DIV | CR := CR / 操作数 | DIV 4 | |
| 比较运算 | GT | CR := (CR > 操作数) | GT 100 |
| GE | CR := (CR >= 操作数) | GE 50 | |
| EQ | CR := (CR = 操作数) | EQ 0 | |
| NE | CR := (CR <> 操作数) | NE 0 | |
| LT | CR := (CR < 操作数) | LT 10 | |
| LE | CR := (CR <= 操作数) | LE 10 | |
| 控制流 | JMP | 无条件跳转到标签 | JMP NEXT |
| JMPC | CR为TRUE时跳转 | JMPC NEXT | |
| JMPCN | CR为FALSE时跳转 | JMPCN NEXT | |
| CAL | 调用功能块/程序 | CAL FB1 | |
| RET | 返回调用点 | RET |
4.2.2 操作流程:修饰符与括号的优先级规则
IL的修饰符主要有两个:N(取反)和C(条件执行)。N修饰操作数,C修饰操作符本身。例如:
ANDN %IX0.1:CR := CR AND (NOT %IX0.1)LDN %IX0.1:CR := NOT %IX0.1STN %QX0.0:将CR的取反值存储到%QX0.0JMPC NEXT:如果CR为TRUE,跳转到NEXT标签
括号的优先级规则为:括号内的表达式优先计算,且括号可以嵌套。例如:
LD %IX0.0
AND ( %IX0.1 OR %IX0.2 )
ST %QX0.0
这段代码的含义是:%QX0.0 = %IX0.0 AND (%IX0.1 OR %IX0.2)。执行时,遇到左括号,CR值(%IX0.0)被压栈,然后计算括号内的%IX0.1 OR %IX0.2,结果与栈顶值(%IX0.0)相与,最终存入CR。
4.2.3 案例拆解:带定时器的混合控制逻辑
考虑一个工业场景:传送带启动后,若检测到物料(传感器Sensor为TRUE),则启动电机,并在3秒后自动停止。使用IL和定时器功能块实现:
LD Start_PB (* 启动按钮 *)
ST Conveyor_Run (* 传送带运行 *)
LD Sensor (* 检测物料 *)
ST Timer_IN (* 启动定时器输入 *)
CAL TON_Inst (* 调用定时器功能块,参数在背景数据块中 *)
LD TON_Inst.Q (* 读取定时器输出 *)
ST Motor_Run (* 控制电机 *)
LD Conveyor_Run
ANDN TON_Inst.Q (* 3秒后停止传送带 *)
ST Conveyor_Run
这个案例展示了IL如何与功能块(FB)交互。通过CAL指令调用定时器,并通过背景数据块传递参数。IL的文本形式使得这种调用非常直观,且便于在文本编辑器中批量修改。
4.2.4 常见误区与总结
误区1:混淆ST与S/R。 ST是直接赋值,每次执行都会更新目标;S和R是置位/复位指令,仅在CR为TRUE时执行一次,且具有保持特性。在需要锁存逻辑时,应使用S/R。
误区2:忽略比较运算的结果类型。 比较运算的结果是布尔值,但操作数可以是整数、实数等。确保比较操作数的数据类型一致,否则可能触发隐式转换或编译错误。
经验提醒:在IL中使用括号时,建议将复杂的逻辑表达式拆分为多个中间变量,例如先计算Temp := %IX0.1 OR %IX0.2,再执行AND Temp。这比深层嵌套括号更易读、易调试。
4.3 IL程序示例:从简单逻辑到状态机
4.3.1 核心原理:IL的模块化与结构化编程
虽然IL是低级语言,但通过功能块(FB)和函数(FC),可以实现模块化设计。IL程序可以调用其他POU,并通过参数传递数据。此外,IL支持局部变量和全局变量,使得代码组织更加清晰。
4.3.2 操作流程:设计一个简单的顺序功能块
以一个两工位装配站的顺序控制为例,要求:按下启动按钮后,工位1夹紧→工位2钻孔→工位1松开→工位2退回,循环执行。使用IL实现状态机:
(* 状态定义:0=空闲, 1=夹紧, 2=钻孔, 3=松开, 4=退回 *)
LD Start_PB
AND Current_State = 0
JMPC RUN_INIT
LD Current_State
EQ 1
AND Clamp_Done
JMPC RUN_DRILL
LD Current_State
EQ 2
AND Drill_Done
JMPC RUN_RELEASE
LD Current_State
EQ 3
AND Release_Done
JMPC RUN_RETRACT
LD Current_State
EQ 4
AND Retract_Done
JMPC RUN_IDLE
JMP END_PROG
RUN_INIT:
LD TRUE
ST Clamp_Out
LD 1
ST Current_State
JMP END_PROG
RUN_DRILL:
LD TRUE
ST Drill_Out
LD 2
ST Current_State
JMP END_PROG
RUN_RELEASE:
LD FALSE
ST Clamp_Out
LD 3
ST Current_State
JMP END_PROG
RUN_RETRACT:
LD FALSE
ST Drill_Out
LD 4
ST Current_State
JMP END_PROG
RUN_IDLE:
LD 0
ST Current_State
JMP END_PROG
END_PROG:
RET
这个示例展示了IL如何通过JMP和标签实现状态跳转。虽然代码较长,但逻辑清晰,每个状态的处理独立成段,便于维护。
4.3.3 案例拆解:IL与结构化文本(ST)的混用
在实际工程中,IL常与ST混用。例如,用ST编写复杂的数学运算,用IL编写底层逻辑。IEC 61131-3允许在IL中调用ST编写的函数:
LD Raw_Value
CAL Scale_FC (IN := Raw_Value, OUT := Scaled_Value)
LD Scaled_Value
GT 100.0
ST Alarm_High
这种混用方式兼顾了IL的底层控制能力和ST的算法表达能力。
4.3.4 常见误区与总结
误区1:滥用跳转导致“意大利面条式代码”。 过多的JMP会使程序难以阅读。建议使用结构化编程方法,将状态机封装在功能块内,用IL实现状态转移,但避免跨功能块的跳转。
误区2:忽略变量初始化。 IL程序中的变量在首次扫描前必须初始化,否则可能产生随机值。在PLC中,全局变量通常在启动时初始化,但局部变量需在POU中显式初始化。
经验提醒:在IL中实现状态机时,建议使用枚举类型定义状态,而不是裸整数。这能提高代码可读性,并减少因状态值写错导致的逻辑错误。
4.4 IL与汇编语言的对比:同构与异构
4.4.1 核心原理:两者的本质差异
IL常被称为“PLC的汇编语言”,但两者在抽象层次、数据类型、可移植性和应用场景上存在显著差异。下表从多个维度进行对比:
| 对比维度 | IL(指令表) | 汇编语言 |
|---|---|---|
| 面向对象 | 控制逻辑(布尔、定时器、计数器) | CPU指令(寄存器、内存地址) |
| 数据类型 | 强类型(BOOL, INT, REAL, TIME等) | 弱类型(字节、字、双字) |
| 隐式机制 | 累加器(CR) | 累加器(如EAX)或通用寄存器 |
| 可移植性 | IEC 61131-3标准定义,跨PLC品牌 | 与CPU架构强相关(x86, ARM等) |
| 硬件访问 | 通过I/O映射访问物理端子 | 直接操作内存映射I/O或端口 |
| 调试手段 | 在线监控变量、强制I/O | 断点、单步、寄存器查看 |
| 典型应用 | 工业控制、逻辑联锁、顺序控制 | 驱动开发、操作系统内核、嵌入式启动 |
| 代码密度 | 较高(一行一操作) | 高(一行可含多个操作) |
| 学习曲线 | 较平缓(面向控制工程师) | 陡峭(需理解CPU体系结构) |
4.4.2 判断方法:何时选择IL而非其他语言
在IEC 61131-3的框架下,选择IL的决策依据如下:
- 逻辑密集且顺序敏感:如安全联锁、紧急停机逻辑,IL的顺序执行特性便于逐行审查。
- 资源受限的PLC:老式或小型PLC可能不支持ST或FBD,IL是唯一文本选择。
- 与既有代码兼容:许多遗留系统使用IL编写,维护时需保持语言一致。
- 需要精确控制执行顺序:IL的显式顺序优于FBD的隐式网络顺序。
4.4.3 案例拆解:将一段C语言逻辑翻译为IL
假设有一段C语言逻辑:if (a && !b) { c = 1; } else { c = 0; },其中a、b、c均为布尔变量。翻译为IL:
LD a
ANDN b
ST c
这个翻译展示了IL的简洁性。但若逻辑更复杂,如if ((a || b) && !c) { d = 1; },则IL为:
LD a
OR b
ANDN c
ST d
这种翻译过程体现了IL与汇编在“条件分支”处理上的差异:汇编需要显式的比较和跳转指令,而IL通过布尔运算直接得出结果,无需跳转。
4.4.4 常见误区与总结
误区1:认为IL可以完全替代汇编。 IL无法直接操作硬件寄存器、中断向量或内存地址,这些仍需汇编或C语言。IL的抽象层次高于汇编,但低于ST。
误区2:忽视IL的扫描周期特性。 与汇编的连续执行不同,IL程序在PLC中按扫描周期执行,输出在周期结束时统一刷新。这可能导致“同一周期内多次读取同一输入”得到相同值,与汇编的实时读取不同。
总结复盘:IL语言是IEC 61131-3中唯一与汇编语言有直接对应关系的文本语言。它的优势在于底层逻辑的透明性和执行顺序的可控性,劣势在于复杂算法表达困难。在实际工程中,建议将IL用于逻辑联锁、状态机跳转和底层I/O处理,而将ST用于数学运算和数据处理,两者结合可发挥最大效能。理解IL与汇编的异同,有助于工程师在硬件层面理解PLC的工作机制,从而编写出更高效、更可靠的程序。