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

下面这张图能帮你理清它们的关系:

组织块 OB 程序调度中心 功能 FC 无记忆,用完即走 功能块 FB 有记忆,自带数据 全局 DB 数据共享 背景 DB FB专属数据 可访问 自动关联 选择原则 需要记忆状态 → FB | 纯计算逻辑 → FC | 定时执行 → OB | 数据共享 → DB 提示:一个FB可以对应多个背景DB实例,实现多对象复用

4.6 实际项目中的选择策略

说了这么多理论,来点实际的。我在做自动化产线项目时,通常这样分配:

  1. OB1:主循环,调用所有FC和FB
  2. OB35:每100ms执行一次,做PID调节和高速采集
  3. FC:模拟量转换、报警判断、数据校验、通信解析
  4. FB:电机控制、阀门控制、气缸控制、输送带控制
  5. 全局DB:设备参数、工艺配方、系统状态
  6. 背景DB:每个FB实例自动生成,存运行数据

一个小技巧:如果你不确定某个功能该用FC还是FB,先写成FC。后期发现需要记忆状态了,改成FB只需要加一个VAR块,代码逻辑基本不用动。反过来,FB改FC就麻烦多了。

好了,关于POU的概念和区别就聊到这里。记住一句话:OB决定什么时候跑,FC/FB决定怎么跑,DB决定跑的数据从哪来、存哪去。把这四个角色搞清楚了,你的程序结构自然就清晰了。


公众号:蓝海资料掘金营,微信deep3321