4、指令表(IL)编程:IL语法、操作符、程序示例、与汇编语言的对比

指令表(Instruction List,IL)是IEC 61131-3标准中定义的五种编程语言之一,属于文本化编程语言。它源于欧洲PLC厂商(如西门子STL、ABB指令表)的长期实践,以类似汇编语言的助记符形式描述控制逻辑。IL语言的核心价值在于:它提供了一种与硬件指令集高度对应、执行顺序明确、调试直观的编程方式,特别适合逻辑密集、时序敏感、对执行效率有要求的控制任务。在IEC 61131-3的框架下,IL被定义为一种“低级”语言,但通过标准化的操作符和修饰符,它依然具备结构化编程的能力。本章将深入解析IL的语法体系、操作符语义、程序组织方式,并通过与汇编语言的对比,帮助工程师理解IL的底层逻辑与适用边界。

IL指令表语言核心结构图 IL程序结构 • 程序组织单元(POU) • 标签(Label): 跳转目标 • 指令行: [标签] 操作符 [修饰符] [操作数] [注释] • 累加器(CR): 隐式结果存储 • 括号表达式: 控制优先级 操作符分类 • 加载/存储: LD, ST • 逻辑运算: AND, OR, XOR, NOT • 算术运算: ADD, SUB, MUL, DIV • 比较运算: GT, GE, EQ, NE, LT, LE • 控制流: JMP, CAL, RET, ) • 修饰符: N(取反), C(条件), N C 与汇编语言对比 • 相似: 助记符、顺序执行 • 差异: IL面向控制逻辑 • 差异: 隐式累加器(CR) • 差异: 数据类型安全 • 差异: 可移植性(标准定义) • 差异: 无硬件寄存器操作 IL执行模型 ① 从第一条指令开始顺序执行 ② 操作结果存入累加器(CR) ③ 下一条指令读取CR作为隐式操作数 ④ 遇到JMP/CAL/RET时改变执行流 ⑤ 括号表达式改变运算优先级 ⑥ 最终结果通过ST指令输出到变量 IL代码示例(电机启停控制) LD Start_PB (* 读取启动按钮 *) OR Motor_Run (* 自保持 *) ANDN Stop_PB (* 停止按钮取反 *) ST Motor_Run (* 输出到电机 *)

上图展示了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程序的执行遵循严格的顺序扫描规则:

  1. 初始化:程序启动时,CR被清零,所有变量保持初始值。
  2. 顺序执行:从程序的第一条指令开始,逐行执行,直到遇到RET(返回)指令或程序结束。
  3. 括号处理:当遇到左括号(时,当前CR值被压入内部堆栈,括号内的表达式独立计算,遇到右括号)时,堆栈中的值弹出并与括号内结果进行运算。
  4. 跳转与调用: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将操作数加载到CRLD %IX0.0
ST将CR存储到操作数ST %QX0.0
S将操作数置位(CR为TRUE时)S Motor_Run
R将操作数复位(CR为TRUE时)R Motor_Run
逻辑运算ANDCR := CR AND 操作数AND %IX0.1
ORCR := CR OR 操作数OR %IX0.2
XORCR := CR XOR 操作数XOR %IX0.3
NOTCR := NOT CR(单目)NOT
算术运算ADDCR := CR + 操作数ADD 5
SUBCR := CR - 操作数SUB 3
MULCR := CR * 操作数MUL 2
DIVCR := CR / 操作数DIV 4
比较运算GTCR := (CR > 操作数)GT 100
GECR := (CR >= 操作数)GE 50
EQCR := (CR = 操作数)EQ 0
NECR := (CR <> 操作数)NE 0
LTCR := (CR < 操作数)LT 10
LECR := (CR <= 操作数)LE 10
控制流JMP无条件跳转到标签JMP NEXT
JMPCCR为TRUE时跳转JMPC NEXT
JMPCNCR为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.1
  • STN %QX0.0:将CR的取反值存储到%QX0.0
  • JMPC 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的工作机制,从而编写出更高效、更可靠的程序。