功能块封装(FB):创建标准功能块的核心步骤

各位工程师朋友,今天我们来聊聊功能块封装。说实话,我刚入行那会儿,写PLC程序都是"一把梭"——所有逻辑堆在一个OB里,看着挺爽,调试起来真要命。后来我慢慢意识到,没有标准化的功能块,风电程序维护起来就是一场噩梦。

功能块(FB),说白了就是一段可复用的程序模块。它有自己的"内存",能记住上次运行的状态。这一点跟函数(FC)不一样——FC每次调用完就"失忆"了。在风电领域,变桨控制、偏航控制、转矩计算这些核心逻辑,我强烈建议都用FB来封装。

创建标准功能块的步骤

我个人习惯按这五步走,你试试看,效率会高很多:

  1. 需求分析——搞清楚这个FB要干什么。输入是什么?输出是什么?需要记住哪些中间状态?
  2. 接口定义——确定输入参数、输出参数、静态变量。这一步很关键,接口定好了,后面就顺了。
  3. 逻辑实现——写核心算法。我建议先画流程图,再写代码。
  4. 测试验证——用模拟数据跑一遍,看看输出对不对。
  5. 文档化——写清楚每个接口的含义、取值范围、注意事项。

核心原则:一个FB只做一件事,并且把它做好。别想着一个FB解决所有问题,那叫"上帝对象",维护起来你会哭的。

接口定义:输入/输出/静态变量

接口设计是FB的灵魂。我见过太多人把接口搞得乱七八糟,结果调用的时候自己都搞不清哪个是哪个。

接口类型 说明 典型例子
输入(Input) 每次调用时从外部传入的数据 风速、桨叶角度、发电机转速
输出(Output) FB计算后返回给调用者的结果 目标变桨角度、故障代码
静态变量(Static) FB内部保存的状态,下次调用时还能用 积分累加值、上次输出值、计数器

我的经验:静态变量命名时加个前缀,比如"st_",一眼就能看出是内部状态。我曾经接手过一个项目,静态变量和临时变量混着用,排查问题花了整整两天。

案例:变桨角度计算FB

好了,理论说完了,咱们来点实际的。变桨控制是风电控制的核心,我拿这个例子给你拆解一下。

这个FB的功能是:根据当前风速和发电机转速,计算出目标变桨角度。逻辑很简单——风速超过额定风速时,开始变桨,限制风轮吸收的能量。

接口定义

名称 类型 数据类型 说明
WindSpeed Input REAL 当前风速(m/s)
GenSpeed Input REAL 发电机转速(rpm)
RatedWindSpeed Input REAL 额定风速(m/s)
PitchAngleSet Output REAL 目标变桨角度(°)
ErrorCode Output INT 故障代码(0=正常)
st_Integral Static REAL PI控制器的积分项
st_LastOutput Static REAL 上次输出值,用于限幅

核心逻辑实现

嗯,这里要注意:变桨角度计算通常用PI控制器。比例项响应快,积分项消除静差。我当年在海上风电项目里,就因为积分项没处理好,导致变桨系统来回震荡,那叫一个头疼。

// 变桨角度计算FB - 核心逻辑(SCL语言)
// 功能:根据风速偏差计算目标变桨角度

// 1. 计算风速偏差
#WindSpeedDeviation := #WindSpeed - #RatedWindSpeed;

// 2. 如果风速低于额定风速,不変桨
IF #WindSpeedDeviation <= 0.0 THEN
    #PitchAngleSet := 0.0;
    #st_Integral := 0.0;  // 复位积分项
    #ErrorCode := 0;
    RETURN;
END_IF;

// 3. PI控制计算
// 比例项
#ProportionalPart := #Kp * #WindSpeedDeviation;

// 积分项(带限幅)
#st_Integral := #st_Integral + (#Ki * #WindSpeedDeviation * #CycleTime);
// 积分限幅,防止积分饱和
IF #st_Integral > #IntegralMax THEN
    #st_Integral := #IntegralMax;
ELSIF #st_Integral < #IntegralMin THEN
    #st_Integral := #IntegralMin;
END_IF;

// 4. 总输出 = 比例 + 积分
#PitchAngleSet := #ProportionalPart + #st_Integral;

// 5. 输出限幅(变桨角度范围:0°~90°)
IF #PitchAngleSet > 90.0 THEN
    #PitchAngleSet := 90.0;
ELSIF #PitchAngleSet < 0.0 THEN
    #PitchAngleSet := 0.0;
END_IF;

// 6. 故障检测
#ErrorCode := 0;
IF #WindSpeed < 0.0 OR #GenSpeed < 0.0 THEN
    #ErrorCode := 1;  // 输入信号异常
END_IF;

避坑指南:我曾经在北方一个风场调试,冬天温度低,风速传感器偶尔会报负值。如果没做输入有效性检查,积分项会直接飞掉,变桨角度瞬间打到90°,机组直接停机。所以,输入信号的有效性检查一定要做,别偷懒。

FB调用示例

在主程序里调用这个FB,就像这样:

// 主程序循环中调用变桨角度计算FB
"PitchControlFB_Instance"(
    WindSpeed := "HMI_WindSpeed",        // 来自风速传感器的实时值
    GenSpeed := "Drive_GenSpeed",        // 来自变频器的转速信号
    RatedWindSpeed := 12.5,              // 额定风速,固定参数
    PitchAngleSet => "Pitch_Demand",     // 输出给变桨驱动器
    ErrorCode => "Pitch_Error"           // 故障状态
);

你想想看,如果不用FB,每次都要重写一遍PI控制逻辑,多累啊。封装成FB后,一个实例搞定一台机组,多台机组就多建几个实例,互不干扰。

知识体系结构图

下面这张图,是我梳理的FB封装核心逻辑,你看一眼就能明白整体脉络:

功能块封装(FB)知识体系 功能块封装(FB) 创建步骤 ① 需求分析 ② 接口定义 ③ 逻辑实现 ④ 测试验证 ⑤ 文档化 接口类型 输入(Input) 输出(Output) 静态变量(Static) 案例:变桨角度计算 输入:风速、转速 输出:目标角度 算法:PI控制 限幅:0°~90° 核心原则:高内聚、低耦合、一个FB只做一件事

这张图把FB封装的核心要素都串起来了。你写FB的时候,就照着这个框架来,不会跑偏。

最后说一句:FB封装不是一蹴而就的。我刚开始也封装得不好,后来迭代了七八个版本才稳定下来。别怕改,关键是每次改完要更新文档,不然三个月后你自己都看不懂。


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