功能块封装(FB):创建标准功能块的核心步骤
各位工程师朋友,今天我们来聊聊功能块封装。说实话,我刚入行那会儿,写PLC程序都是"一把梭"——所有逻辑堆在一个OB里,看着挺爽,调试起来真要命。后来我慢慢意识到,没有标准化的功能块,风电程序维护起来就是一场噩梦。
功能块(FB),说白了就是一段可复用的程序模块。它有自己的"内存",能记住上次运行的状态。这一点跟函数(FC)不一样——FC每次调用完就"失忆"了。在风电领域,变桨控制、偏航控制、转矩计算这些核心逻辑,我强烈建议都用FB来封装。
创建标准功能块的步骤
我个人习惯按这五步走,你试试看,效率会高很多:
- 需求分析——搞清楚这个FB要干什么。输入是什么?输出是什么?需要记住哪些中间状态?
- 接口定义——确定输入参数、输出参数、静态变量。这一步很关键,接口定好了,后面就顺了。
- 逻辑实现——写核心算法。我建议先画流程图,再写代码。
- 测试验证——用模拟数据跑一遍,看看输出对不对。
- 文档化——写清楚每个接口的含义、取值范围、注意事项。
核心原则:一个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的时候,就照着这个框架来,不会跑偏。
最后说一句:FB封装不是一蹴而就的。我刚开始也封装得不好,后来迭代了七八个版本才稳定下来。别怕改,关键是每次改完要更新文档,不然三个月后你自己都看不懂。
公众号:蓝海资料掘金营,微信deep3321