PLC 编程 8 项最佳实践
PLC 编程远不只是把逻辑块串起来。做了 10 多年自动化工程师,我见过无数 PLC 项目——很多都有典型的毛病:
- 看不懂的变量名,比如
M0.0、DB1.DBX0.0或%MW100 - 编程语言混用——同一个项目里 LAD、FBD、STL、SCL 乱成一锅粥
- 文档缺失——最初的程序员早就离职了。
你知道吗?可以在TIA Portal中使用 逆向文档插件为PLC程序补上缺失的文档!
在这篇文章里,我分享在西门子 TIA Portal 中编写可维护 PLC 程序最重要的最佳实践。
目标读者
本文面向具备 TIA Portal 基础知识的 PLC 程序员。如果你是初学者,我建议先上一门 IEC 61131-3 基础课程。
1、为什么 PLC 编程需要最佳实践?
1.1 问题:遗留代码
我实践中的典型场景:
设备制造商打来电话:"我们的产线停了,能帮帮忙吗?"
到现场后的残酷现实:
- 变量名像
M0.0、DB1.DBX0.0、%MW100——没人知道是什么意思 - LAD、FBD、STL、SCL 在同一个项目里混用
- 没有注释,没有结构
- 最初的程序员几年前就离开公司了
排查故障花了远超必要的时间,因为没人看得懂这段代码。
1.2 解决方案:专业化编程
结构化、可维护的 PLC 程序带来明显优势:
- 描述性变量名让排障更快
- 新程序员上手时间更短
- 更好的错误处理带来更少的停机时间
当现有程序正是上面这副模样时
下面这八项实践在新项目里可以从第一天就用起来——在一个已经生长了多年的程序里则需要回头改造,而这很少能在日常工作中顺带完成。结构、命名约定和一个带版本的块库,我会作为定义清楚的工作包来承接,必要时连同现有项目的代码审查一起做:TIA Portal PLC 编程服务。
2、最佳实践 1:始终遵循 IEC 61131-3 标准
IEC 61131-3 是 PLC 编程的国际标准:它统一定义了语言、数据类型和块类型。它的实际价值在于:按标准写出的代码,对下一个程序员来说依然可读。
2.1 五种编程语言概览
| 标准 (IEC) | 西门子 | 适用场景 | 局限 |
|---|---|---|---|
| LD – 梯形图 | KOP | 联锁、简单逻辑 | 超过约 50 个网络后难以维护 |
| FBD – 功能块图 | FUP | 控制、数据流逻辑 | 大程序中难以维护 |
| IL – 指令表 | AWL | 仅遗留代码 | 2013 年起已弃用 |
| ST – 结构化文本 | SCL | 算法、计算、库 | 对纯联锁不够直观 |
| SFC – 顺序功能图 | GRAPH | 顺序控制 (GRAFCET) | 简单逻辑下开销过大 |
常见误区: SCL 和 ST 是同一种语言——SCL 是西门子对 Structured Text 的实现。把两者分开列等于一种语言数了两次,还通常把 IL 挤掉了。
我的建议
80% 的代码用 SCL/ST(逻辑、计算、顺序控制),只在简单联锁上用 LAD(例如急停逻辑)。控制回路用 FBD,复杂状态机用 SFC。
如果你希望把遗留系统里的LAD转成SCL,可以使用TIA Portal的 LAD2SCL 插件!
2.2 结构化编程:金字塔
OB1 (Main)
└── FC_SystemControl (Operating modes)
├── FB_PackMLStateMachine (Sequential control)
│ ├── FB_MotorControl (Component)
│ ├── FB_ValveControl (Component)
│ └── FB_ConveyorControl (Component)
└── FC_AlarmHandler (Utility)
规则: 每一层只承担一项明确的职责。
3、最佳实践 2:建立命名约定
3.1 问题:变量名混乱
反面例子(来自实践):
// ❌ BAD
M0.0 // What is this?
DB1.DBX0.0 // Which motor?
%MW100 // Temperature? Pressure?
正面例子:
// ✅ GOOD
bMotorPumpMainRun : BOOL; // Main pump motor running
iTemperatureTank1 : INT; // Tank 1 temperature in °C
rPressureSetpoint : REAL; // Pressure setpoint in bar
3.2 标准化前缀(匈牙利命名法)
| 前缀 | 数据类型 | 示例 |
|---|---|---|
b |
BOOL | bMotorRun, bAlarmActive |
i |
INT | iCounter, iTemperature |
r |
REAL | rSpeed, rPressure |
s |
STRING | sAlarmText, sRecipeName |
dt |
DATE_TIME | dtStartTime, dtLastMaintenance |
t |
TIME | tDelayStart, tCycleTime |
udi |
UDINT | udiPartCounter, `udiTotalProduction |
3.3 带前缀的输入/输出
// Inputs: ix (Input X)
ixMotorFeedbackRun : BOOL AT %I0.0; // "x" = Bool
iwTemperatureSensor : WORD AT %IW0; // "w" = Word
// Outputs: qx (Output X)
qxMotorStart : BOOL AT %Q0.0;
qwValvePosition : WORD AT %QW0;
一致性至上: 和团队敲定一种约定并写进文档。
工具支持
西门子 TIA Portal v18+ 提供 Code Quality Check,可自动检查命名约定。
4、最佳实践 3:正确使用功能块与函数
4.1 功能块 (FB) —— 有状态
什么时候用?
- 有状态的部件(电机、阀门、输送机)
- 告警处理
- 状态机
示例:电机控制
FUNCTION_BLOCK FB_MotorControl
VAR_INPUT
bStart : BOOL; // Start command
bStop : BOOL; // Stop command
bFeedback : BOOL; // Motor running feedback
END_VAR
VAR_OUTPUT
qxStart : BOOL; // Motor start output
bRunning : BOOL; // Status: Motor running
bAlarm : BOOL; // Fault active
END_VAR
VAR
tDelayStart : TON; // Start delay
tTimeoutFeedback : TON; // Feedback timeout
bAlarmLatched : BOOL; // Latched fault
END_VAR
// Program logic here...
END_FUNCTION_BLOCK
调用:
// In OB1 or FC
dbMotorPumpMain(
bStart := bSystemRun AND NOT bEmergencyStop,
bStop := bSystemStop OR bEmergencyStop,
bFeedback := ixMotorPumpMainFeedback
);
qxMotorPumpMainStart := dbMotorPumpMain.qxStart;
4.2 函数 (FC) —— 无状态
什么时候用?
- 无状态的计算
- 量程换算
- 数学运算
- 工具函数
示例:温度量程换算
FUNCTION FC_ScaleTemperature : REAL
VAR_INPUT
iwRawValue : INT; // Raw sensor value (0-27648)
rMinTemp : REAL; // Minimum temperature (e.g., -50°C)
rMaxTemp : REAL; // Maximum temperature (e.g., +150°C)
END_VAR
// Scaling 0-27648 → rMinTemp to rMaxTemp
FC_ScaleTemperature := rMinTemp +
(REAL#iwRawValue / 27648.0) * (rMaxTemp - rMinTemp);
END_FUNCTION
调用:
rTemperatureTank1 := FC_ScaleTemperature(
iwRawValue := iwTempSensor1,
rMinTemp := -50.0,
rMaxTemp := 150.0
);
经验法则
调用之间有状态(记忆)吗?→ FB 是计算或工具函数?→ FC
5、最佳实践 4:按 NAMUR NE 107 做错误处理
NAMUR NE 107 是过程自动化中自监控与诊断的行业标准,在机械工程领域也已普及。
5.1 四种状态
5.2 用 FB_AlarmHandler 实现
FUNCTION_BLOCK FB_AlarmHandler
VAR_INPUT
bTrigger : BOOL; // Alarm trigger
eAlarmLevel : INT; // 1=Info, 2=Warning, 3=Error, 4=Critical
sAlarmText : STRING[80]; // Alarm text
END_VAR
VAR_OUTPUT
bAlarmActive : BOOL; // Alarm is active
bAckRequired : BOOL; // Acknowledgment required
END_VAR
VAR
bAlarmLatched : BOOL; // Latched alarm
dtTimestamp : DATE_TIME; // Alarm timestamp
END_VAR
// Logic: Latch alarm, set timestamp, acknowledge if needed
IF bTrigger AND NOT bAlarmLatched THEN
bAlarmLatched := TRUE;
dtTimestamp := CURRENT_TIMESTAMP();
bAlarmActive := TRUE;
// Logging to HMI/SCADA
// ...
END_IF;
bAckRequired := bAlarmLatched AND eAlarmLevel >= 3;
END_FUNCTION_BLOCK
最佳实践: 一个中央告警管理器收集所有告警,并打包发送给 HMI。
注意
每条告警都需要唯一 ID(例如 ALM-001),并在文档中有对应说明。
6、最佳实践 5:用 Git 做版本控制
是的,Git 对 PLC 代码同样管用!从 TIA Portal v18 起,集成变得简单多了。
6.1 配置:TIA Portal + Git
1. 启用 Multiuser Engineering
TIA Portal → Project → Properties → Multiuser Engineering → Activate
2. 以 Git 友好的格式保存项目
# .gitignore for TIA Portal
*.ap18
*.zap18
__OPNB*
*.bak
3. 初始化 Git 仓库
git init
git add .
git commit -m "Initial commit: TIA Portal project setup"
6.2 PLC 项目的分支策略
main (Production code)
├── develop (Development)
│ ├── feature/motor-control-update
│ ├── feature/add-safety-logic
│ └── bugfix/alarm-text-typo
└── hotfix/critical-emergency-stop-bug
工作流:
- 创建特性分支:
git checkout -b feature/new-conveyor-logic - 提交更改:
git commit -m "Add conveyor start delay 2s" - 创建拉取请求(GitHub、GitLab)
- 由第二位程序员做代码评审
- 合并进
develop - 在开发 PLC 上测试
- 发布分支 →
main
优势
- 团队协作不丢数据
- 历史版本随时可取回
- 不同特性可并行开发
- 自动备份(GitHub、GitLab Cloud)
7、最佳实践 6:注释与文档
7.1 代码注释:三行规则
反例:
// ❌ Obvious, adds nothing
bMotorRun := TRUE; // Start motor
正例:
// ✅ Explains WHY, not WHAT
// 2s delay due to pressure build-up in hydraulic circuit
// (See requirement REQ-HYD-012)
tDelayStart(IN := bStartRequest, PT := T#2S);
qxMotorStart := tDelayStart.Q;
7.2 文档标准
每个 FB/FC 都要有:
(*
Name: FB_MotorControl
Version: 1.2.0
Author: David Prybisch
Date: 2025-11-22
Description:
Standardized motor control with start delay,
feedback monitoring and fault latching.
Change History:
v1.2.0 - 2025-11-22 - Added timeout monitoring
v1.1.0 - 2025-10-15 - NAMUR alarm integration
v1.0.0 - 2025-09-01 - Initial version
*)
FUNCTION_BLOCK FB_MotorControl
// ...
END_FUNCTION_BLOCK
系统架构用 UML 图:
用 PlantUML 或 Microsoft Visio 之类的工具把整体架构可视化。
8、最佳实践 7:测试与仿真
8.1 用 PLCSIM 仿真
西门子 PLCSIM Advanced 允许在没有物理硬件的情况下测试 PLC 程序:
工作流:
- 在 TIA Portal 中开发 PLC 程序
- 启动 PLCSIM Advanced 并加载项目
- 通过监控表设置并检查变量
- 系统性地测试顺序控制
优势:
- 尽早发现错误——在办公室而不是客户现场
- 新程序员可以放心测试
- 调试前先验证更改
小技巧
用 TIA Portal 的监控表系统性地强制置位输入/输出,检查程序行为。
9、最佳实践 8:性能优化
9.1 控制 CPU 占用率
常见的性能杀手:
- 定时器/计数器太多 → 改用数组而不是单个变量
- 实时循环里的字符串操作 → 挪到低优先级任务
- OB1 里的复杂计算 → 改用循环中断 (OB35)
示例:基于数组的定时器管理
// ❌ BAD: 100 individual timers
VAR
tMotor1 : TON;
tMotor2 : TON;
// ... 98 more timers
END_VAR
// ✅ GOOD: Array of timers
VAR
atMotorTimers : ARRAY[1..100] OF TON;
END_VAR
// Call in loop
FOR i := 1 TO 100 DO
atMotorTimers[i](IN := abMotorStart[i], PT := T#2S);
aqxMotorStart[i] := atMotorTimers[i].Q;
END_FOR;
9.2 用多重实例优化内存
问题: 每个 FB 实例都消耗自己的 DB(实例 DB)。
解决方案: 对重复出现的块使用多重实例。
// FB_ConveyorLine contains 10x FB_MotorControl as multi-instance
FUNCTION_BLOCK FB_ConveyorLine
VAR
Motor1 : FB_MotorControl; // Multi-instance
Motor2 : FB_MotorControl;
// ...
END_VAR
// Create only ONE instance of FB_ConveyorLine
dbConveyorLine : FB_ConveyorLine; // Saves 90% memory vs. 10 separate FBs
10、干净的代码在排障时能省多少
上面每一条最佳实践,最迟在产线停机、必须顶着压力找故障时回本。结构化工作的程序员不是漫无目的地"找"故障——而是逐层收敛:症状 → 受影响的块 → 信号路径 → 根因。当每个工艺段都封装在自己名副其实的 FB 里,你立刻就知道该查哪里,而不必在 OB1 的几千个网络里刨。
这就是前几章 FC/FB 分离、命名约定和清晰层级的实际回报:它们把几个小时的猜测变成几分钟的定向排查。
深入: 完整方法——从告警视图到块的五个步骤、合适的 TIA Portal 工具、为什么强制置位几乎从来不是答案、以及大多数问题背后的五类故障——见专栏文章 PLC 故障排查:5 步找到根因。
11、结论:专业的 PLC 编程物有所值
对结构化、可维护 PLC 程序的投入有多重回报:
- 描述性变量名和清晰结构让排障更快
- 新程序员上手时间更短
- 更好的错误处理带来更少的停机时间
- 全生命周期内维护更省力
11.1 下一个 PLC 项目的检查清单
- 命名约定已与团队商定并成文
- 层级化程序结构已规划(UML 图)
- 遵循 IEC 61131-3 标准
- 已按 NAMUR NE 107 实现告警管理
- Git 仓库已建立、
.gitignore已配置 - PLCSIM 仿真已准备就绪
- 代码评审流程已建立
- 文档(FB 头、系统架构)已编写
下一步
从小处着手:拿一个现有项目,按这些最佳实践重构一个 FB。投入的时间(2-4 小时)会在下一次排障时回本。
12、信息图:PLC 最佳实践一览
原文链接: PLC Programming: 8 Best Practices for Maintainable Code
汇智网翻译整理,转载请标明出处