4、程序组织单元(POU):组织块(OB)、功能(FC)、功能块(FB)、数据块(DB)的概念与区别
各位同行,今天我们来聊聊PLC编程里最核心的一个概念——程序组织单元,也就是POU。
说实话,我刚入行那会儿,被OB、FC、FB、DB这几个缩写搞得晕头转向。明明都是写代码的块,干嘛分这么多种?后来踩了不少坑才明白,这玩意儿就像工具箱里的不同工具——螺丝刀、扳手、钳子各有各的用处。用对了事半功倍,用错了……嗯,调试的时候有你受的。
4.1 什么是程序组织单元(POU)?
POU是IEC 61131-3标准定义的基本编程单元。说白了,就是你在PLC里写的每一段代码、每一个功能模块,都叫POU。
标准把POU分成三类:
- 组织块(OB)——程序的入口,系统自动调用
- 功能(FC)——无记忆的代码块,用完就忘
- 功能块(FB)——有记忆的代码块,带自己的数据
再加上一个特殊的数据容器——数据块(DB),虽然它不算严格意义上的POU,但实际项目中我们天天跟它打交道。
核心理解:OB是骨架,FC/FB是血肉,DB是血液。骨架决定程序怎么跑,血肉实现具体功能,血液存储流动的数据。
4.2 组织块(OB)——程序的调度中心
OB是PLC操作系统的入口。你写的所有代码,最终都要被某个OB调用才能执行。
我习惯把OB想象成工厂里的调度员。他手里拿着一个任务清单,到点了就喊一声:「该干活了!」然后对应的工人(FC/FB)就开始工作。
常见的OB类型:
| OB编号 | 类型 | 执行时机 | 典型用途 |
|---|---|---|---|
| OB1 | 主循环组织块 | 循环执行 | 主程序逻辑 |
| OB35 | 循环中断 | 固定时间间隔 | PID控制、高速采样 |
| OB40~47 | 硬件中断 | 输入信号跳变 | 急停、编码器计数 |
| OB100 | 启动组织块 | CPU启动时 | 初始化参数 |
| OB82 | 诊断中断 | 模块故障时 | 故障处理 |
避坑指南:我曾经在一个项目中,把大量计算逻辑直接写在OB35里。结果OB35每10ms执行一次,CPU负载直接飙到85%。后来把计算逻辑移到OB1,OB35只做信号采集,负载降到30%。记住:中断OB里代码越短越好,别在里面做复杂运算。
4.3 功能(FC)——用完即走的工具
FC没有自己的数据存储区。每次调用时,输入输出参数都在栈里临时分配,调用结束就释放了。
你想想看,这就像你借别人的计算器用一下。用完还回去,下次再用还是那个计算器,但上面不会保留你上次算的结果。
FC最适合做:
- 纯逻辑运算(比如数据类型转换)
- 数学计算(求平均值、最大值)
- 没有记忆需求的通用功能
// FC示例:计算两个数的平均值
FUNCTION FC_CalcAverage : REAL
VAR_INPUT
value1 : REAL;
value2 : REAL;
END_VAR
VAR_OUTPUT
average : REAL;
END_VAR
average := (value1 + value2) / 2.0;
FC_CalcAverage := average;
END_FUNCTION
我的习惯:所有工具类函数都写成FC。比如模拟量转换、报警判断、数据校验。这样每个FC都是独立的,拿到任何项目里都能直接用。
4.4 功能块(FB)——带记忆的智能模块
FB和FC最大的区别:FB有自己的背景数据块。每次调用FB,系统都会分配一块专用的数据区给它。
这就像你买了一个智能电饭煲。它有自己的内存,记得你上次设定的煮饭模式、预约时间。下次开机,这些设置还在。
FB的典型应用场景:
- 电机控制(需要记忆启停状态、运行时间)
- 阀门控制(需要记忆开度、累计动作次数)
- PID调节(需要记忆积分项、上次偏差)
- 计时器、计数器(本身就是带记忆的)
// FB示例:电机控制功能块
FUNCTION_BLOCK FB_MotorControl
VAR_INPUT
start_cmd : BOOL;
stop_cmd : BOOL;
fault_reset : BOOL;
END_VAR
VAR_OUTPUT
running : BOOL;
fault : BOOL;
run_hours : REAL;
END_VAR
VAR
state : INT := 0; // 内部状态记忆
timer : TON; // 内部定时器
END_VAR
CASE state OF
0: // 停止状态
IF start_cmd THEN
state := 1;
timer(IN:=TRUE, PT:=T#2S);
END_IF;
1: // 启动中
IF timer.Q THEN
running := TRUE;
state := 2;
END_IF;
2: // 运行中
IF stop_cmd THEN
running := FALSE;
state := 0;
END_IF;
END_CASE;
END_FUNCTION_BLOCK
注意:FB的实例数量直接影响内存占用。我曾经见过有人把同一个FB调用了500次,结果DB占了几十兆。合理做法是:只实例化真正需要的数量,用数组管理同类对象。
4.4 数据块(DB)——程序的数据库
DB是专门用来存数据的。它不包含任何代码逻辑,就是一块结构化的内存区域。
DB分两种:
- 全局数据块(Global DB)——所有OB、FC、FB都能访问
- 背景数据块(Instance DB)——专属于某个FB,其他块不能直接访问
我个人习惯把全局DB当作项目的「中央数据库」。所有设备参数、工艺配方、报警信息都放在里面。这样维护起来特别方便,改一个地方,所有用到的地方都生效。
// 全局DB示例:设备参数
DATA_BLOCK DB_EquipmentParams
STRUCT
conveyor_speed : REAL := 50.0; // 传送带速度
heater_temp : REAL := 120.0; // 加热温度
pressure_max : REAL := 10.0; // 最大压力
alarm_enable : BOOL := TRUE; // 报警使能
END_STRUCT
END_DATA_BLOCK
4.5 四者关系与选择原则
说了这么多,到底什么时候用FC,什么时候用FB?我总结了一个简单原则:
- 需要记忆状态? → 用FB
- 只是算个结果? → 用FC
- 需要定时执行? → 放到OB里
- 数据需要共享? → 用DB
下面这张图能帮你理清它们的关系:
4.6 实际项目中的选择策略
说了这么多理论,来点实际的。我在做自动化产线项目时,通常这样分配:
- OB1:主循环,调用所有FC和FB
- OB35:每100ms执行一次,做PID调节和高速采集
- FC:模拟量转换、报警判断、数据校验、通信解析
- FB:电机控制、阀门控制、气缸控制、输送带控制
- 全局DB:设备参数、工艺配方、系统状态
- 背景DB:每个FB实例自动生成,存运行数据
一个小技巧:如果你不确定某个功能该用FC还是FB,先写成FC。后期发现需要记忆状态了,改成FB只需要加一个VAR块,代码逻辑基本不用动。反过来,FB改FC就麻烦多了。
好了,关于POU的概念和区别就聊到这里。记住一句话:OB决定什么时候跑,FC/FB决定怎么跑,DB决定跑的数据从哪来、存哪去。把这四个角色搞清楚了,你的程序结构自然就清晰了。
公众号:蓝海资料掘金营,微信deep3321