程序写到第 5000 行,改一个变量的地址,全线崩盘。新人接手老项目,看 3 天代码找不到逻辑。设备出故障,A 段程序改了影响 B 段——这些都是"程序没结构"的典型症状。
F2 第 10 章调研中强调:专业工程师和业余程序员的差距不在编程技巧,而在程序结构。业余程序员写"一锅端"——所有逻辑堆在 OB1 主程序里。专业工程师写"模块化"——每个功能独立封装,互不干扰。
这篇文章我讲 3 招:FB(功能块)+ FC(函数)+ DB(数据块)——西门子 S7-1200/1500 的"程序架构三件套"。学会这 3 招,程序可读性提升 5 倍,调试时间减半,团队协作无障碍。
目录
一、程序结构崩盘的 4 个征兆
征兆 1:变量命名混乱
OB1 里用 M0.0、M1.0、M100.0,没有注释,半年后没人知道 M100.0 是啥。
征兆 2:双线圈问题反复出现
不同网络给同一变量赋值,前面的逻辑被默默失效。改了 A 处影响 B 处。
征兆 3:调试不敢动一段程序
“这段代码谁都不敢动,怕改了设备崩”——程序已经脆弱到无法维护。
征兆 4:新人看不懂
同事离职,新人接手,看 1 个月代码也理不清逻辑——因为没结构。
⚠️ 避坑警告:程序没结构是技术债,越欠越多。第 1 个月写程序简单,第 6 个月调试地狱,第 12 个月重写整个项目。专业工程师从第一天就考虑结构。
二、FB 功能块:把功能"封装"成积木
2.1 FB 是什么?
FB(Function Block,功能块)——把"一种功能"封装成独立模块。FB 有自己的内存(Instance DB),可以"记住"状态。
打个比方:FB 像一个智能家电——你按下启动按钮(输入),它自己决定怎么工作(内部逻辑 + 状态),最后告诉你结果(输出)。同一个 FB 可以做"电机 1"、“电机 2”、“电机 3”——每个实例独立,状态不互相干扰。
2.2 FB 的 3 大特征
特征 1:有自己的内存
- 每个 FB 调用都生成一个背景 DB(Instance DB),存储 FB 内部的局部变量
- FB_A 调用 1 次 = FB_A_Instance_1(独立的内存)
- FB_A 调用 2 次 = FB_A_Instance_2(另一个独立的内存)
- 两个实例完全独立,互不影响
特征 2:可参数化
- 输入(IN)、输出(OUT)、输入输出(IN_OUT)参数可配置
- 同一个 FB,控制不同的电机只需传不同的参数(如启动按钮地址、停止按钮地址、热继电器地址)
特征 3:可重用
- 写一个 FB_电机启停,在多个电机控制中复用
- 改一处 → 所有调用都更新(节省开发时间)
2.3 一个 FB 的标准结构
FUNCTION_BLOCK "FB_电机启停"
VAR
// 静态变量(断电保持)
running : BOOL; // 电机运行状态
fault : BOOL; // 故障状态
END_VAR
BEGIN
// 启动逻辑:按下启动按钮 + 没故障 + 没停止 → SET running
IF start_btn AND NOT fault AND NOT stop_btn THEN
running := TRUE;
END_IF;
// 停止逻辑:按停止按钮 / 有故障 → RST running
IF stop_btn OR fault THEN
running := FALSE;
END_IF;
// 输出
motor_run := running;
END_FUNCTION_BLOCK
关键点:FB 内部用局部变量(running、fault),不用全局 M 区。这就是"封装"——外部只能通过 IN/OUT 接口访问 FB。
2.4 FB 调用方式
// OB1 主程序
"FB_电机启停_DB"(
start_btn := "start_1", // 输入:启动按钮
stop_btn := "stop_1", // 输入:停止按钮
fault := "fault_1", // 输入:故障信号
motor_run => "motor_1_run" // 输出:电机运行
);
"FB_电机启停_DB2"(
start_btn := "start_2",
stop_btn := "stop_2",
...
);
结果:调用 1 次就控制 1 个电机,调用 2 次就控制 2 个电机——完全不写重复代码。
三、FC 函数:可重用的代码片段
3.1 FC 是什么?
FC(Function,函数)——把"一段计算逻辑"封装成可重用模块。FC 没有自己的内存,每次调用都是同样的输入同样的输出。
打个比方:FC 像计算器——你输入 2+3,它返回 5。下次输入 5+7,它返回 12。计算器不"记住"任何事。
3.2 FC 的 3 大特征
特征 1:无内存
- 每次调用 FC,临时变量(VAR_TEMP)用完即释放
- FC 不能"记住"上次调用的结果
特征 2:必须有返回值
- FC 的输出参数(Output、Return)是必须传的
- 调用 FC 时必须处理返回值
特征 3:纯函数化
- FC 内部不依赖任何"状态"——输入确定则输出确定
- 测试 FC 比测试 FB 容易得多
3.3 FC 典型应用:模拟量线性转换
FUNCTION "FC_线性转换" : REAL
VAR_INPUT
raw_value : INT; // 原始值(0-27648 对应 4-20mA)
raw_min : INT := 0;
raw_max : INT := 27648;
eng_min : REAL; // 工程量最小值(如 0℃)
eng_max : REAL; // 工程量最大值(如 100℃)
END_VAR
BEGIN
// 4-20mA 对应 0-27648,转 0-100℃
"FC_线性转换" := (raw_value - raw_min) / (raw_max - raw_min) * (eng_max - eng_min) + eng_min;
END_FUNCTION
调用:
tank_temp := "FC_线性转换"(
raw_value := "AI_temp",
eng_min := 0.0,
eng_max := 100.0
);
好处:转换逻辑写一次,所有模拟量都能用。改转换算法只需改 FC 内部。
3.4 FB vs FC 怎么选
| 场景 | 用 FB | 用 FC |
|---|---|---|
| 电机控制(要"记住"运行状态) | ✓ | |
| 模拟量转换(无状态) | ✓ | |
| PID 控制(要"记住"积分项) | ✓ | |
| 通信数据解析(无状态) | ✓ | |
| 顺序控制(要"记住"当前步) | ✓ | |
| 报警判断(无状态) | ✓ |
💡 效率技巧:口诀:要"记住"状态用 FB,不要状态用 FC。
四、DB 数据块:参数和工艺数据管理
4.1 DB 是什么?
DB(Data Block,数据块)——存储数据的"容器"。FB 调用必须配 Instance DB;全局数据用 Global DB。
4.2 DB 的 3 种类型
Instance DB(背景数据块):
- 每个 FB 调用自动生成一个
- 存储 FB 内部的局部变量
- FB 和它的 Instance DB 是"绑定的"
Global DB(全局数据块):
- 手工创建,存储全局数据
- 类似 C 语言的全局变量
- 所有程序块都能访问
System DB(系统数据块):
- PLC 自动生成,存储系统配置
- 用户不能修改
4.3 Global DB 的典型用法:工艺参数
DATA_BLOCK "DB_工艺参数"
VAR
tank_temp_setpoint : REAL := 80.0; // 罐温目标值 80℃
tank_temp_hyst : REAL := 2.0; // 滞回 2℃
pump_pressure_max : REAL := 5.0; // 泵最大压力 5bar
cycle_time : TIME := T#30s; // 循环时间 30 秒
END_VAR
好处:
- 所有工艺参数集中在一处,改一处就改全部
- HMI 上显示参数一目了然
- 不同产品配方切换只需切换 DB
4.4 F2 推荐:参数分层管理
第 1 层:硬件配置(CPU、模块)→ TIA Portal 设备组态 第 2 层:IO 标签(I0.0、Q0.0)→ PLC 变量表 第 3 层:工艺参数(温度、压力、流量)→ Global DB 第 4 层:功能模块(电机、PID)→ FB + Instance DB 第 5 层:主程序(OB1 调用所有 FB)→ 简洁
💡 效率技巧:这种分层结构让程序可读性提升 5 倍。F2 第 11 章专门讲 PLC 编程规范,分层管理是核心思想。
五、西门子三层架构:程序架构最佳实践
5.1 西门子三层架构图
┌─────────────────────────┐
│ OB1 主程序(顶层) │
│ - 调用 FB │
│ - 全局逻辑 │
└─────────────┬───────────┘
↓
┌─────────────────────────┐
│ FB 功能块(中层) │
│ - FB_电机启停 │
│ - FB_PID 控制 │
│ - FB_通信解析 │
│ - FB_顺序控制 │
└─────────────┬───────────┘
↓
┌─────────────────────────┐
│ FC 函数 + DB 数据(底层)│
│ - FC_线性转换 │
│ - FC_数值滤波 │
│ - DB_工艺参数 │
│ - DB_配方管理 │
└─────────────────────────┘
5.2 三层架构的工程价值
价值 1:可读性
- OB1 只调用 FB,不写具体逻辑
- 看 OB1 就能知道系统有几个功能模块
- 进入 FB 看具体实现
价值 2:可维护性
- 改一个电机控制只需改 FB_电机启停
- 不影响其他电机
- 改工艺参数只需改 DB_工艺参数
价值 3:可重用性
- 写一个 FB_电机启停,所有电机都用
- 项目之间复用(不同项目可能有相同的电机控制)
价值 4:可测试性
- FB 可以单独测试(仿真器模拟输入)
- 不依赖整个系统
价值 5:团队协作
- 多人开发:A 写 FB_电机,B 写 FB_PID,互不干扰
- 模块负责人清晰
5.3 OB1 主程序的最佳实践
// OB1 主程序(精简版)
// 注释:所有控制逻辑由 FB 封装,OB1 只做调度
"FB_系统初始化"(); // 系统初始化
"FB_安全监控"(); // 安全监控
"FB_电机1控制"(); // 电机 1
"FB_电机2控制"(); // 电机 2
"FB_PID 温度控制"(); // 温度控制
"FB_通信解析"(); // 通信
"FB_HMI 更新"(); // HMI 更新
"FB_报警处理"(); // 报警
OB1 简洁到极致——只有 FB 调用,一目了然。
5.4 三层架构 vs 一锅端
| 对比项 | 一锅端 | 三层架构 |
|---|---|---|
| 程序行数 | 5000+ | OB1: 50-100, FB: 各 100-500 |
| 调试时间 | 8 小时 | 1-2 小时 |
| 团队协作 | 困难 | 顺畅 |
| 5 年后维护 | 几乎无法维护 | 仍可维护 |
| 新人上手 | 1 个月 | 1 周 |
⚠️ 避坑警告:"一锅端"是业余程序员的标志。如果你发现 OB1 超过 1000 行,说明程序需要重构了。F2 第 11 章明确:OB1 超过 500 行 = 架构有问题。
六、5 大典型 FB 拆分案例
案例 1:FB_电机启停(最常用)
输入:start_btn、stop_btn、fault、reset 输出:motor_run、motor_fault 应用:所有电机控制(传送带、风机、泵、压缩机)
案例 2:FB_阀门控制
输入:open_btn、close_btn、open_limit、close_limit、fault 输出:valve_open、valve_close 应用:所有气动/电动阀门
案例 3:FB_PID 控制
输入:setpoint、process_value、kp、ki、kd 输出:output 应用:温度、压力、流量、液位控制
案例 4:FB_顺序控制(SFC)
输入:start、stop、fault、step_done[] 输出:current_step、step_command[] 应用:生产线顺序动作
案例 5:FB_通信解析
输入:raw_data[] 输出:parsed_value[] 应用:Modbus、PROFINET、EtherCAT 通信
💡 效率技巧:F2 推荐的 FB 设计原则:单一职责 + 接口清晰 + 内部状态独立。每个 FB 只做一件事,外部通过 IN/OUT 通信,内部状态不外泄。
八、FB/FC 重入(Reentrant)的工程陷阱
FB 重入 = 一个 FB 同时被多个调用者执行
S7-1200 默认 FB 不可重入。如果 OB1 和 OB35(循环中断 100ms)同时调用同一个 FB → CPU 报错或数据错乱。
解决:西门子 FB 属性的"块可重入"勾选。三菱用多个 Instance DB 实现。
不可重入的风险:
- PID 积分项在 OB1 和中断里同时累加 → 积分翻倍
- 计数器同时被两个调用者 +1 → 丢计数
- HMI 读写值到一半被中断打断 → 读到中间值
九、PLC 编程规范 6 条(F2 第 11 章提炼)
1. 单一职责原则
每个 FB 只做一件事。"FB_电机控制"和"FB_电机状态诊断"分开。
2. 命名统一原则
- 动宾结构:
valve_open、motor_start、temp_read - BOOL 加 is/has 前缀:
is_running、has_fault - 常数大写下划线:
MAX_TEMP、MIN_PRESSURE
3. 全局变量最小化
全局变量 ≤ 10 个(IO 除外)。FB 间通过 IN/OUT 参数通信。
4. OB1 ≤ 100 行
超过 100 行 → 重构拆 FB。F2 第 11 章说 OB1 超过 500 行 = 架构有问题。
5. 注释即文档
每个 Network 第一行写注释(“电机 1 启动逻辑”)。每个变量声明写注释(“罐温设定值,℃”)。
6. 版本号
每个 FB 内部注释版本号 + 修改日期 + 修改人。// V1.2 2026-08-11 张三:增加超时保护
十、工业程序质量评估的 5 个维度
| 维度 | 优秀程序 | 糟糕程序 |
|---|---|---|
| 可读性 | 看程序 = 看文档,30 分钟理解 | 看 3 天也看不懂 |
| 可维护性 | 改一处只影响一个 FB | 改一处崩全局 |
| 可测试性 | 每个 FB 可独立测试 | 必须全套联动才能测 |
| 鲁棒性 | 异常输入 / 断电 / 通信中断都有处理 | 任何异常 = 失控 |
| 可扩展性 | 加一个电机 = 复制 FB + 改 IO | 加一个电机要改 100 行程序 |
F2 第 10 章的结论:专业工程师和业余程序员的差距不在编程技巧,在程序结构——业余的写一锅端,专业的写模块化。
十一、常量与配方管理:工艺参数 DB 的最佳实践
配方 DB 设计模式
DATA_BLOCK "DB_配方_饼干"
VAR
recipe_name : STRING[20] := '巧克力饼干';
oven_temp : REAL := 180.0; // 烤炉温度 ℃
belt_speed : REAL := 2.5; // 传送带速度 m/s
baking_time : TIME := T#12m; // 烘烤时间
cooling_time : TIME := T#3m; // 冷却时间
END_VAR
配方切换流程
- HMI 选配方号 → PLC 从配方数组加载目标配方 → 复写到"运行参数"DB
- 运行参数 DB 是实时用的参数(可在线修改),配方 DB 是只读库(不可随意改)
- 两层分离:运行参数 ≠ 配方库 ≠ 离线备份
F2 推荐:配方数量 > 50 个时用上位机数据库(SQL)管理,PLC 只保留当前运行配方。
十二、多人协作 PLC 编程的工作流标准
Git 下的 PLC 项目版本管理
西门子 TIA Portal → 导出 .awl/.scl 源码 → Git 管理 → PR/MR 审核 → 合并 → 导入 TIA 编译。
三菱 GX Works3 → 导出 ST 文本 → Git 管理。
团队分支策略:
- main:生产版本(最终下载到 PLC)
- develop:开发版本
- feature/FB_xxx:某功能开发分支
合并前必须由另一人 Review——两双眼睛比一双眼睛找到的 bug 多 3 倍。
编码规范自动化
用西门子 Openness 写一个 Python 脚本批量检查:变量命名符合规范?注释存在?双线圈?F2 第 11 章总结:编码规范写在文件里有人看但没人执行,自动化检查才真正落地。
十三、程序优化的 5 个量化指标
| 指标 | 差 | 及格 | 优秀 |
|---|---|---|---|
| OB1 行数 | > 500 | < 200 | < 100 |
| 全局变量数 | > 100 | < 50 | < 10 |
| 每 FB 行数 | > 200 | < 100 | < 50 |
| 函数参数数 | > 10 | < 8 | < 5 |
| 循环嵌套 | > 3 层 | ≤ 2 层 | ≤ 1 层 |
F2 第 10 章结论:程序不是"能跑就行"——能跑只是第 1 层,能改 = 第 2 层,团队能协作 = 第 3 层,5 年后还能维护 = 第 4 层。
十四、PLC 代码审查 7 问
- 每个 FB 只做一件事?(单一职责)
- 全局变量 ≤ 10 个?(最小化全局状态)
- OB1 ≤ 100 行?
- 每个变量有注释?
- 有无双线圈?(编译警告零容忍)
- 有无 SET/OUT 混用?
- 安全回路独立于 PLC 程序?
7 个全 Yes → 优秀程序。有 1 个 No → 改完再上线。
十五、PLC 程序设计的"反模式"
- “上帝 FB”:一个 FB 1000 行包揽所有逻辑 → 拆成 10 个 FB
- “马蜂窝 IF”:IF 嵌套 5 层 → CASE/状态机替代
- “散弹式变量”:到处读写同一个全局变量 → 封装成 FB 接口
- “硬编码”:限值/延时直接写在梯形图里 → 提到 DB 常量化
- “注释反着写”:
// motor_stop := TRUE比// 停止电机有用 10 倍——注释说"为什么",不说"做了什么"
十六、PLC 代码审查 7 问与反模式
代码审查 7 问
- 每个 FB 只做一件事? 2. 全局变量 ≤ 10? 3. OB1 ≤ 100 行? 4. 每个变量有注释? 5. 无编译警告(双线圈)? 6. 无 SET/OUT 混用? 7. 安全回路独立于 PLC?
7 全 Yes → 优秀程序。有 1 个 No → 改完再上线。
必须避免的 5 种反模式
- “上帝 FB”:一个 FB 1000 行包揽所有逻辑 → 拆成 10 个
- “马蜂窝 IF”:IF 嵌套 5 层 → CASE/状态机替代
- “散弹式变量”:到处读写同一个全局变量 → 封装成 FB 接口
- “硬编码”:限值/延时直接写梯形图 → 提到 DB 常量化
- “废话注释”:
// 停止电机→ 应写// 故障信号触发后延时 2s 停止以防管道水锤
十七、Git 与 PLC 项目的版本管理
西门子 TIA Portal → 导出 .awl/.scl 源码 → Git → PR 审核 → 合并 → 导入编译。三菱 → 导出 ST 文本 → Git。
分支策略:main = 生产版本、develop = 开发版本、feature/FB_xxx = 某功能。
合并前必须另一人 Review——两双眼睛发现的 bug 比一双多 3 倍。
十八、程序量化评估标准
| 指标 | 差 | 及格 | 优秀 |
|---|---|---|---|
| OB1 行数 | > 500 | < 200 | < 100 |
| 全局变量 | > 100 | < 50 | < 10 |
| 每 FB 行数 | > 200 | < 100 | < 50 |
| 函数参数 | > 10 | < 8 | < 5 |
| 循环嵌套 | > 3 | ≤ 2 | ≤ 1 |
F2 结论:能跑 = 第 1 层,能改 = 第 2 层,团队能协作 = 第 3 层,5 年后还能维护 = 第 4 层。大部分程序停在第 1 层。
扩展阅读 + 系列预告
本篇核心要点回顾:
- 程序崩盘 4 征兆:变量混乱、双线圈反复、不敢改段、新人看不懂
- FB 三特征:有内存、可参数化、可重用
- FC 三特征:无内存、必须有返回值、纯函数化
- FB vs FC:要"记住"状态用 FB,不要状态用 FC
- 三层架构:OB1 调用 FB,FB 调用 FC 和 DB
思考题:
- 你的 OB1 主程序有多少行?有没有超过 500 行?是否需要重构?
- 你的项目里有几个 FB/FC?它们是按"功能"拆分的还是按"组件"拆分的?
下一篇预告: 第 11 篇《4-20mA 信号飘成心电图?3 类模拟量干扰 + 4 招硬核解决方案》——模拟量干扰源、屏蔽层单端接地、信号隔离器应用。
标签:#PLC编程 #FB功能块 #FC函数 #数据块 #西门子 #梯形图 #自动化架构
SEO 关键词:PLC程序结构、FB功能块、FC函数、数据块、西门子编程

318

被折叠的 条评论
为什么被折叠?



