做 PLC 编程时,你有没有过这种感受:项目小的时候,代码怎么写都顺;一旦项目变大,比如要控制十几台设备、处理上百个信号,代码就开始 “一团乱麻”—— 找个变量要翻半天,改个逻辑怕影响其他功能,后期维护更是头大。
其实解决这个问题的关键,就藏在 IEC61131-3 标准里的 “POU” 概念里。今天就把 POU 讲透,帮你把复杂的 PLC 程序 “拆成小块”,代码既清晰又好维护。
一、POU 是什么?为什么它能让代码变清晰?
POU 是 “程序组织单元”(Program Organization Unit)的缩写,简单说就是 IEC61131-3 标准规定的 “代码基本单元”。
就像盖房子要先做砖、梁、板这些预制构件,POU 就是 PLC 编程里的 “预制构件”—— 把整个程序拆成一个个独立的 POU,每个 POU 负责一个具体功能(比如 “电机启停控制”“温度数据滤波”“定时器计时”),最后把这些 POU 像搭积木一样组合起来,就是一个完整的控制系统。
这种拆分方式的核心优势在于:职责明确、接口清晰、可复用。不管项目多复杂,每个 POU 只干 “一件事”,后期修改或维护时,只需针对特定 POU 操作,不用动整个程序,极大降低了出错概率。
IEC61131-3 里定义了 3 种核心 POU,分别是 Program(程序)、Function(功能) 和 Function Block(功能块)。这三者分工明确,配合起来用,再复杂的程序也能理得清清楚楚。
二、逐个拆解:POU 三大组件的 “职责” 和用法
1. Program(程序):整个 PLC 的 “总指挥”
定位
POU 里的 “顶层单元”,相当于整个 PLC 系统的 “总指挥”,负责对接硬件 I/O 口(比如读取传感器信号、控制执行器动作),并协调其他 POU(功能、功能块)的工作。
关键字与示例
用 PROGRAM 开头,END_PROGRAM 结尾,中间包含变量声明和核心逻辑:
PROGRAM Main_Control // 主程序名:Main_Control // 声明输入变量(外部信号传入) VAR_INPUT Start_Button: BOOL; // 启动按钮(布尔型,True=按下,False=松开) Stop_Button: BOOL; // 停止按钮(布尔型) Emergency_Stop: BOOL;// 急停按钮(布尔型) END_VAR // 声明输出变量(控制外部设备) VAR_OUTPUT Motor_Run: BOOL; // 电机运行输出(True=运行,False=停止) Alarm_Light: BOOL; // 报警灯输出(True=亮,False=灭) END_VAR // 核心逻辑:按钮控制电机启停,急停优先 IF Emergency_Stop THEN Motor_Run := FALSE; Alarm_Light := TRUE; ELSE Alarm_Light := FALSE; Motor_Run := Start_Button AND NOT Stop_Button; END_IF; END_PROGRAM |
核心特点
- 对接硬件:能直接存取 PLC 的 I/O 口,是其他 POU 和硬件之间的 “桥梁”,比如上面的程序直接读取按钮信号,控制电机和报警灯;
- 多主程序支持:在多 CPU 的 PLC 控制系统中,能同时执行多个 Program,比如一个 CPU 负责 “加热控制”,另一个负责 “输送控制”,各自对应一个 Program;
- 变量自动管理:无需手动给变量分配存储地址(如传统 PLC 中的 “MW200”“I0.0”),编程系统会自动管理,减少地址冲突风险。
2. Function(功能):无 “记忆” 的 “计算工具”
定位
负责 “输入数据→处理→输出结果” 的纯计算单元,没有 “记忆” 功能 —— 只要输入参数一样,输出结果就一定一样,就像计算器:不管什么时候算 “10÷2”,结果都是 5。
关键字与示例
用 FUNCTION 开头,END_FUNCTION 结尾,必须指定 “返回数据类型”(比如实数、整数),结果通过 “功能名” 返回:
// 功能:两数相除,避免除以0(返回值类型为实数 REAL) FUNCTION Divide: REAL VAR_INPUT A: REAL; // 被除数(实数) B: REAL; // 除数(实数) Default: REAL := 0.0; // 默认值(B=0时返回,默认0.0) END_VAR // 核心逻辑:判断除数是否为0,避免错误 IF B <> 0 THEN Divide := A / B; // 结果赋值给功能名(Divide) ELSE Divide := Default; // 除数为0时返回默认值 END_IF END_FUNCTION |
核心特点
- 无记忆性:每次调用都是 “重新计算”,不保留上一次的执行状态,比如调用 Divide(10,2),无论调用多少次,结果都是 5;
- 只有输入变量:没有输出变量,最终结果通过 “功能名” 返回(如上面的 Divide 就是结果);
- 高复用性:适合封装重复的计算逻辑,比如数据转换(摄氏度转华氏度)、数学运算(求平均值)、字符串处理(截取子串),调用时直接传参数即可。
3. Function Block(功能块):有 “记忆” 的 “功能单元”
定位
比 Function 更灵活的 “功能单元”,不仅有输入、输出变量,还能 “记住” 上一次的执行状态,就像一个带 “缓存” 的模块,比如定时器:启动后会一直累加时间,直到复位,下次调用时能延续之前的计时状态。
关键字与示例
用 FUNCTION_BLOCK 开头,END_FUNCTION_BLOCK 结尾,可包含输入、输出、内部变量:
// 功能块:简单定时器(计时到设定值后输出Done信号) FUNCTION_BLOCK Timer_Block VAR_INPUT Start: BOOL; // 启动信号(True=开始计时) Reset: BOOL; // 复位信号(True=重置时间) Time_Set: TIME; // 设定时间(如T#5s=5秒) END_VAR VAR_OUTPUT Done: BOOL; // 计时完成输出(True=计时到) Current_Time: TIME; // 当前计时(实时显示) END_VAR VAR_TEMP // 内部临时变量(仅功能块内部使用,外部不可见) Is_Running: BOOL := FALSE; // 是否正在计时 END_VAR // 核心逻辑:复位→启动→计时→完成 IF Reset THEN Current_Time := T#0s; // 复位:时间清0 Done := FALSE; Is_Running := FALSE; ELSIF Start THEN Is_Running := TRUE; // 每执行一次,时间累加100ms(需配合任务周期) Current_Time := Current_Time + T#100ms; // 计时到设定值,输出Done IF Current_Time >= Time_Set THEN Done := TRUE; Is_Running := FALSE; END_IF ELSE // 未启动且未复位,保持当前状态 IF Is_Running THEN Current_Time := Current_Time + T#100ms; IF Current_Time >= Time_Set THEN Done := TRUE; Is_Running := FALSE; END_IF END_IF END_IF END_FUNCTION_BLOCK |
核心特点
- 有记忆性:内部变量(如上面的 Current_Time Is_Running)会保留上一次的状态,比如定时器启动后,即使暂停调用,再次启动时会从之前的时间继续累加;
- 变量更丰富:有输入、输出变量,还能有内部变量(VAR_TEMP 或普通变量),内部逻辑 “对外隐藏”,使用者只需关注输入输出;
- 适合状态控制:常用于需要 “保持状态” 的场景,比如定时器、计数器、电机启停控制(记住当前运行 / 停止状态)、报警复位逻辑,是 PLC 编程中用得最多的 POU。
三、关键对比:Function 和 Function Block 别搞混!
很多新手容易把这两者弄混,记住 3 个核心区别,再也不迷糊:
对比维度 | Function(功能) | Function Block(功能块) |
记忆性 | 无记忆,输入相同则输出必相同 | 有记忆,相同输入可能因 “历史状态” 输出不同(如定时器第二次调用时时间延续) |
变量类型 | 只有输入变量,结果靠 “功能名” 返回 | 有输入、输出变量,可包含内部变量 |
调用限制 | 不能调用 Function Block | 可以调用 Function |
举个直观例子:用 Function 写 “温度转换”(摄氏度转华氏度),输入 25℃ 永远返回 77℉;用 Function Block 写 “加热控制”,输入 “启动” 信号后,会记住当前温度是否达到设定值,下次调用时不会 “忘记” 之前的加热状态。
四、实战建议:怎么用 POU 让代码更高效?
- 先拆分功能,再写代码
拿到项目后,别直接上手写逻辑,先把功能拆成 “小单元”:比如 “温度采集”“电机控制”“报警处理”“数据上传”,每个单元对应一个 POU。拆分的原则是 “一个 POU 只干一件事”,避免大而全。
- 按场景选对 POU 类型
- 需要 “状态保持”:用 Function Block(如定时器、设备启停、计数器);
- 需要 “纯计算”:用 Function(如数据转换、数学运算、字符串处理);
- 需要 “对接硬件 + 协调逻辑”:用 Program(主程序只做 “调度”,少写具体逻辑)。
- 主程序只做 “协调”,不做 “细节”
Program 里尽量只调用其他 POU,比如主程序读取 “温度传感器信号” 后,调用 Temp_Filter 功能(滤波处理),再调用 Heater_Control 功能块(控制加热),自己不写滤波和加热的具体逻辑。这样后期改逻辑时,只需改对应的 POU,不用动主程序,降低维护成本。
- 封装常用 POU,建 “自己的库”
把项目中常用的 Function(如 “数据滤波”)和 Function Block(如 “自定义定时器”)整理成库,下次做类似项目时直接导入,不用重复开发,效率翻倍。
你平时用 POU 编程时,有没有遇到过 “分不清功能和功能块” 的情况?评论区说说你的解决方法!