3. PLC编程基础
在 TwinCAT 3 中编写 PLC 程序,不能只停留在“会拖功能块、会连线”的层面。要真正理解程序为什么这样运行、变量为什么这样定义、任务为什么这样分配,就必须回到三个基础问题:PLC 是如何工作的?IEC 61131-3 规定了什么?TwinCAT 3 中的变量类型与程序组织单元如何组织?本节围绕这三个问题展开,为后续编写可维护、可调试的工程代码打下基础。
一、IEC 61131-3 标准:PLC 编程的共同语言
IEC 61131-3 是国际电工委员会制定的可编程控制器编程语言标准,它统一了 PLC 编程的语言体系、数据类型、程序组织方式和变量声明规则。TwinCAT 3 完全支持该标准,并在其基础上扩展了面向对象特性。标准中最核心的内容包括两部分:编程语言与程序组织单元。
IEC 61131-3 定义了五种编程语言,TwinCAT 3 均支持:
- LD(梯形图):以继电器逻辑为原型,适合电气背景工程师阅读和维护。
- FBD(功能块图):以方框和连线表示逻辑,适合表达信号流和运算关系。
- ST(结构化文本):类似 Pascal 的高级语言,适合复杂算法、循环和条件判断。
- IL(指令表):接近汇编的指令序列,现代工程中已较少使用。
- SFC(顺序功能图):以步和转换为核心,适合描述顺序控制流程。
在 TwinCAT 3 工程中,最常见的组合是:用 ST 编写算法和状态机,用 FBD/LD 表达布尔逻辑和信号联锁,用 SFC 组织设备动作顺序。选择语言时,应优先考虑可读性和维护成本,而不是个人偏好。
二、PLC 工作原理与扫描周期
PLC 采用循环扫描方式工作,而不是像普通计算机那样按事件驱动。一个完整扫描周期通常包括三个阶段:
- 输入采样:读取物理输入模块的状态,写入过程映像输入区(%I)。
- 程序执行:按任务顺序执行用户程序,读取输入映像,计算输出映像(%Q)。
- 输出刷新:将输出映像区一次性写入物理输出模块。
这种“先采样、再计算、后输出”的机制决定了 PLC 程序的几个重要特性:输入信号在一个扫描周期内保持不变;输出不会在程序执行中途立即变化;程序执行时间直接影响扫描周期。TwinCAT 3 中,扫描周期由任务(Task)的循环时间决定,例如 1 ms、2 ms 或 10 ms。
关键结论:扫描周期越短,系统响应越快,但 CPU 负载越高。TwinCAT 3 允许为不同任务设置不同周期,例如高速运动控制任务用 1 ms,普通逻辑任务用 10 ms,从而在响应速度与资源占用之间取得平衡。
下面用一张信号链与扫描周期关系图,直观展示从物理输入到物理输出的完整路径,以及过程映像区在其中的位置。
三、变量类型:从 BOOL 到结构体
IEC 61131-3 定义了标准数据类型,TwinCAT 3 在此基础上进行了扩展。变量声明必须遵循“先声明、后使用”的原则,且类型选择直接影响内存占用和运算精度。
| 类型 | 说明 | 典型用途 | 注意事项 |
|---|---|---|---|
| BOOL | 布尔量,1 位 | 按钮、限位、使能信号 | 不可用于模拟量运算 |
| INT / DINT | 16 位 / 32 位整数 | 计数、状态码、索引 | 注意溢出和符号问题 |
| REAL / LREAL | 32 位 / 64 位浮点数 | 温度、速度、位置 | 避免直接用“==”比较 |
| STRING | 字符串 | 配方名、报警文本 | 长度固定,注意内存 |
| ARRAY | 数组 | 批量数据、缓冲区 | 下标从 0 或 1 开始需统一 |
| STRUCT | 结构体 | 设备参数、工艺配方 | 便于整体传递和扩展 |
在 TwinCAT 3 中,变量还分为局部变量和全局变量。局部变量在 POU 内部声明,作用域仅限该 POU;全局变量在 GVL(Global Variable List)中声明,可被多个 POU 访问。工程中应尽量缩小变量作用域,避免全局变量滥用导致耦合过重。
VAR_GLOBAL
bStartBtn : BOOL; // 启动按钮
bStopBtn : BOOL; // 停止按钮
rSetSpeed : REAL := 50.0; // 设定速度
nCycleCount : DINT; // 循环计数
END_VAR
VAR
fbMotor : FB_Motor; // 电机功能块实例
stRecipe : ST_Recipe; // 配方结构体
aBuffer : ARRAY[1..10] OF INT;
END_VAR
四、程序组织单元:POU 的三种形态
IEC 61131-3 将程序组织单元(POU)分为三类:程序(PRG)、功能块(FB)和函数(FUN)。三者的核心区别在于是否具有内部状态、是否可实例化、是否可重复调用。
- PRG(程序):顶层执行单元,通常与任务绑定,每个扫描周期执行一次。一个工程可以有多个 PRG,但应保持结构清晰。
- FB(功能块):具有内部状态,必须实例化后调用。同一个 FB 可以生成多个实例,各自维护独立状态,适合电机、阀门、PID 等设备抽象。
- FUN(函数):无内部状态,相同输入必得相同输出,可直接调用,适合数学运算、类型转换、字符串处理。
选择原则:需要“记住上一次状态”的用 FB;纯计算、无状态的用 FUN;顶层调度和任务入口用 PRG。不要把设备逻辑全部塞进一个 PRG,否则后期无法复用和单元测试。
五、案例:一个电机控制 POU 的组织方式
假设要控制一台电机,包含启动、停止、过载保护和速度设定。合理的组织方式是:定义一个 FB_Motor 功能块,内部封装状态机、定时器和输出逻辑;在 PRG_Main 中实例化两个电机对象,分别对应传送带电机和风机电机。
FUNCTION_BLOCK FB_Motor
VAR_INPUT
bStart : BOOL;
bStop : BOOL;
bOverload: BOOL;
rSpeedSet: REAL;
END_VAR
VAR_OUTPUT
bRun : BOOL;
rSpeedAct: REAL;
bAlarm : BOOL;
END_VAR
VAR
tonStartDelay : TON;
nState : INT;
END_VAR
// 状态机与输出逻辑在此实现
END_FUNCTION_BLOCK
PROGRAM PRG_Main
VAR
fbConveyor : FB_Motor;
fbFan : FB_Motor;
END_VAR
fbConveyor(bStart := bStartBtn, bStop := bStopBtn,
bOverload := bConvOverload, rSpeedSet := rSetSpeed);
fbFan(bStart := bFanStart, bStop := bFanStop,
bOverload := bFanOverload, rSpeedSet := 30.0);
END_PROGRAM
这种组织方式的好处是:电机逻辑只写一次,两个实例互不干扰;调试时可以单独监控某个实例的输入输出;后续增加第三台电机只需再声明一个实例。
六、常见误区与复盘
误区一:把扫描周期当成“实时中断”,认为输入变化会立即影响输出。实际上,输入在采样阶段被冻结,输出在刷新阶段才生效,程序中间的计算不会直接改变物理输出。
误区二:所有变量都声明为全局变量,导致程序之间隐式耦合,修改一处影响多处。应优先使用局部变量和 FB 接口传递数据。
误区三:用 FUN 实现需要保持状态的逻辑,例如计时、计数、滤波。FUN 无内部状态,每次调用都会丢失上次结果,必须改用 FB。
误区四:忽略任务周期与程序执行时间的关系。如果程序执行时间超过任务周期,TwinCAT 会报“任务超时”,系统进入异常状态。应通过任务负载监控和代码优化控制执行时间。