很多做电气自动化的朋友都有这种体会:程序刚开始写的时候很流畅,但一旦设备多了、工艺复杂了、客户改了需求,整个程序就会变得又长又乱。今天这篇文章,我想和你认真聊一聊“PLC 编程框架”这件事。
在通用软件开发领域,“框架”是一个非常成熟的概念,比如 Spring Boot、PyTorch、Pytest 等。而在 PLC 编程里,框架这个说法听起来有点抽象,但其实它并不神秘。简单说,PLC 编程框架就是一套沉淀下来的程序组织规范和功能模块划分标准。
如果你能掌握一套适合自己学习路线和项目类型的 PLC 编程框架,你会发现,从原来“照着图纸一点一点写点位”,慢慢变成“按照架构搭积木”,编程逻辑更清楚,调试效率也会明显提高。这篇文章,我会从框架概念、程序架构、核心模块设计、三菱 PLC 读取和写入变频器频率的实战案例这几个角度完整展开,希望给你一个可以直接落地参考的思路。
1. PLC 程序为什么会越写越乱?
先别急着谈框架,我们先复盘一下,PLC 程序之所以会越写越乱,通常都逃不开下面几个原因。
1.1 所有逻辑都堆在一个程序块里
很多初学者习惯把所有逻辑都写在同一个程序块中,梯形图从上到下几百个网络,中间既有电机启停,又有气缸动作,还有报警连锁和通信处理。程序一旦超过几百行,阅读难度就会急剧上升。
这种写法的最大问题,不是“代码长”,而是缺少分层和边界。别人拿到这份程序,很难快速找到自己关心的那部分逻辑。
1.2 手动、自动、报警、通信混在一起
在实际设备的运行过程中,手动调试和自动运行往往是两套完全不同的控制思路。比如:
- 手动模式下,工程师需要单动气缸、点动电机;
- 自动模式下,设备要按照工艺节拍自动执行;
- 报警状态下,需要打断当前动作并锁定故障信息。
如果这些逻辑没有分区处理,很容易出现“手动切换自动时输出突然跳动”“报警复位后设备乱动”等让人头疼的问题。
1.3 设备点位直接用硬地址
很多老程序里直接写X0、Y1、M0、D10,时间一长,点位多了之后,谁都无法确定D200到底存的是温度还是计数。
这不是说硬地址不能用,而是在程序越来越大之后,应该有符号表、变量表或数据块来统一管理点位和变量,否则程序的可维护性会非常差。
1.4 功能不能复用
同一个设备,比如一台伺服电机控制、一个变频器通信模块,往往会在多个项目中出现。如果每次都重新写一遍,不仅浪费开发时间,而且不同项目中的写法还不一样,后期维护极其痛苦。
把这几个问题放在一起,你就会发现:PLC 编程水平想要提升,其实不是“指令记得多”,而是对程序结构和模块划分的理解够不够深。
2. 认识 PLC 编程框架
2.1 框架的通俗解释
在 PLC 编程里,框架可以理解为一套“程序骨架”。
比如你盖一栋楼,肯定不是直接开始砌墙,而是先做设计图,确定承重结构、水电管线、功能区分布。PLC 程序也是同理,不管你是写西门子、三菱、汇川还是信捷,都应该先有一个固定的程序骨架,然后再往骨架里填充具体逻辑。
这套骨架通常包括:
- 初始化逻辑;
- 输入采样与状态刷新;
- 模式切换调度;
- 手动控制模块;
- 自动控制模块;
- 报警处理模块;
- 通信处理模块;
- 输出刷新模块。
每一个模块解决一类问题,模块和模块之间通过统一的数据区通信,而不是直接交叉跳转。
2.2 有框架和没框架的区别
| 维度 | 没有框架 | 有框架 |
|---|---|---|
| 程序结构 | 阶梯式堆叠,网络顺序混乱 | 分层清晰,模块边界明确 |
| 调试效率 | 找问题靠翻页,效率低 | 按模块定位,快速排查 |
| 程序复用 | 每次项目重写 | 累积功能块,换项目直接调用 |
| 多人协作 | 互相看不懂,改点容易冲突 | 按模块分工,命名规范统一 |
| 稳定性 | 模式切换容易出问题 | 状态管理严谨,互锁明确 |
从工程角度看,框架带来的最大收益,是降低程序复杂度,提高可维护性。
2.3 对指令和硬件的影响
有人可能会担心:用了框架,是不是就要学习更高深的指令?
其实不是。PLC 编程框架更多强调的是组织方式,而不是堆砌高级指令。即使是梯形图编程,也完全可以按框架分页、分组、分程序块来写。三菱 FX 系列如果不用结构化工程,也可以用“梯形图 + 子程序跳转”的方式实现模块化;西门子 S7-1200/1500 则可以直接使用 OB、FC、FB、DB 做到更严谨的模块化。
所以,这套思路在不同品牌之间是通用的,关键是先把概念理顺。
3. 一套通用框架的总体架构
3.1 程序架构简图
下面这张图,是很多项目里比较通用的一套框架结构:
主控制层(主程序 / OB1) ├── 初始化区:开机初始化、数据复位、参数加载 ├── 输入刷新区:采集按钮、传感器、限位等状态 ├── 模式调度区:判断当前处于手动 / 自动 / 回原点 / 急停 ├── 控制逻辑区:根据模式调用对应工艺控制模块 ├── 报警处理区:报警条件判断、报警锁定、报警复位 ├── 输出刷新区:统一驱动 Y 点或 Q 点 └── 通信处理区:HMI、变频器、伺服、上位机数据交换这个结构里最核心的,就是“模式调度”。
3.2 为什么模式调度这么重要
设备运行状态通常不是单一的。
一台自动化设备,往往包含:
- 停机状态;
- 手动调试状态;
- 自动运行状态;
- 回原点状态;
- 报警急停状态。
如果这些状态在程序里没有统一管理,任何一处的跳转条件不严谨,都可能造成误动作。框架的做法是:先把运行模式统一判断出来,再用模式来决定哪些模块可以执行,哪些模块必须封死。
3.3 程序执行的主流程
在不考虑中断和通信抢占的情况下,PLC 程序按照扫描周期循环执行。框架里体现的主流程,一般是:
- 开机初始化;
- 读取输入信号并刷新到中间变量;
- 根据当前模式,调度对应的逻辑模块;
- 执行报警判断和故障处理;
- 刷新输出信号;
- 执行通信任务。
这样一套流程下来,每个扫描周期都走一遍,逻辑清晰,也方便做仿真和联调。
4. 用框架改造程序:程序块与数据区规划
4.1 按品牌划分程序块
西门子 S7-1200/1500
如果你使用 TIA Portal,建议这样规划程序块:
| 程序块类型 | 名称/功能 |
|---|---|
| OB100 | 初始化组织块 |
| OB1 | 主程序组织块 |
| FC | 模式判断、报警处理、通信调度等无状态功能 |
| FB | 电机控制、阀控制、轴控制、变频器控制等功能块 |
| DB | 全局数据块,统一管理输入/输出/报警/参数 |
使用 FB 的好处是,它可以生成自己的背景数据块,每个电机、每个阀都相当于一个对象。举例来说,写一个电机控制 FB,然后在程序里调用多个实例,互不干扰。
三菱 FX 系列
如果使用 GX Works2 或 GX Works3 的简单工程,可以按如下方式组织:
- 主程序和子程序(CALL、CALLP指令)划分功能;
- 用变址或结构体优化数据管理;
- 通信和运动控制尽量独立成子程序模块。
4.2 数据区的统一规划
无论是 PLC 内部软元件、数据寄存器,还是西门子的数据块,建议都按照功能分区。
以三菱 FX 3U 为例,可以这样规划:
| 地址区间 | 用途 |
|---|---|
| M0-M99 | 全局模式标志,比如手动模式、自动模式、报警状态 |
| M100-M199 | 手动控制相关中间变量 |
| M200-M299 | 自动控制相关中间变量 |
| M300-M399 | 报警相关标志 |
| D0-D99 | 设备参数区,比如速度、频率、目标位置 |
| D100-D199 | 实时采集数据区,比如当前频率、电流、温度 |
| D200-D299 | 通信缓冲区,比如和变频器交互的数据区 |
这种规划方式让变量地址变成一种“约定”。以后看任何程序,只要看地址范围,就知道这个变量大概属于什么功能。
4.3 状态定义和模式字
在程序里,不要只用一个 M 点表示“自动”,建议用一个“模式字”统一管理。
例如,定义一个D1000作为模式字:
// 模式字定义,以下数值可自定义枚举 #MODE_STOP := 0; // 停机 #MODE_MANUAL := 1; // 手动模式 #MODE_AUTO := 2; // 自动模式 #MODE_HOME := 3; // 回原点模式 #MODE_ALARM := 4; // 报警状态所有模式切换都通过统一的模式判断逻辑写值,程序内部只读取这个模式字来做调度,这样就不会出现“多个 M 点之间互相竞争”的问题。
4.4 主程序骨架示例
这里我用类似 IEC 61131-3 的结构化文本风格,给大家展示主程序的“骨架”。不同品牌会有语法差异,但思路是一样的:
// 主控制层骨架 CALL 初始化模块; // 首次扫描时初始化 CALL 输入刷新模块; // 刷新按钮、传感器等输入信号 CASE #模式字 OF #MODE_MANUAL: CALL 手动控制模块; #MODE_AUTO: CALL 自动控制模块; #MODE_HOME: CALL 回原点模块; #MODE_STOP: ; ELSE ; END_CASE; CALL 报警处理模块; // 报警统一处理 CALL 输出刷新模块; // 输出统一刷新 CALL 通信处理模块; // 变频器、伺服、HMI 通信你可能会发现,主程序本身非常“薄”,大量逻辑都被拆到了子模块里,这恰恰是框架的价值:主程序只负责调度,不负责具体细节。
5. 核心模块设计
5.1 手动/自动模式切换模块
手动和自动的切换,是 PLC 程序里最敏感的逻辑之一。
一个比较稳妥的做法是:在切换瞬间,先让所有输出回到安全状态,再允许新模式接管输出。
下面是一个简化示例:
// 自动模式切换按钮或 HMI 按钮触发 IF #自动请求 AND NOT #自动运行中 THEN #输出封锁 := TRUE; // 先封锁输出 #模式字 := #MODE_AUTO; // 再切换模式 延时等待 100ms; // 等待输出继电器稳定 #输出封锁 := FALSE; END_IF;这里要注意:手动程序里动作过的设备,进入自动模式前,最好能回到初始状态,否则自动程序一运行,设备可能直接动作,非常危险。
5.2 状态机模块
自动流程最好的组织方式就是状态机。
比如一台搬运机械手,可以拆成以下几个状态:
- 初始状态;
- 下降取料;
- 夹紧;
- 上升;
- 平移;
- 下降放料;
- 松开;
- 上升;
- 回到原点。
在 PLC 框架里,可以把这些状态定义成枚举或整数,然后用CASE语句驱动步骤跳转:
CASE #当前步骤 OF #STEP_INIT: IF #启动允许 THEN #当前步骤 := #STEP_DOWN1; END_IF; #STEP_DOWN1: #气缸1 := TRUE; IF #气缸1到位 THEN #当前步骤 := #STEP_CLAMP; END_IF; #STEP_CLAMP: #夹爪 := TRUE; IF #夹爪夹紧完成 THEN #当前步骤 := #STEP_UP1; END_IF; // 其他步骤省略 END_CASE;状态机的核心优势是:程序的执行顺序一目了然,并且可以通过步号跳转到指定状态,特别适合调试和维护。
5.3 报警处理模块
报警处理如果分散在程序中,排查故障时会非常痛苦。
在框架中,建议把所有报警条件统一放在一个模块中,集中触发、集中复位。每个报警建议使用独立的标志位,例如三菱中对应M3200到M3299,西门子中对应 DB 里的 Bool 数组。
报警处理一般包括:
- 报警触发条件判断;
- 报警锁定,防止瞬间闪烁;
- 报警输出给 HMI 和指示灯;
- 报警复位逻辑,区分手动复位和自动复位。
一个简单的报警示例:
// 报警模块示例 #AL_电机过载 := #电机反馈信号 AND NOT #电机允许; #AL_气压不足 := NOT #气压开关 AND #设备已启动; // 输出报警灯 #报警灯 := #AL_电机过载 OR #AL_气压不足;5.4 通信处理模块
通信在现代 PLC 项目中越来越常见,尤其是变频器、伺服、扫码枪、视觉系统、上位机 MES 等。
通信处理模块的主要任务包括:
- 周期性读写远程设备数据;
- 解析和封装报文;
- 超时和错误重试;
- 与主控逻辑之间的数据交换。
通信模块要尽量独立,不要和工艺逻辑混在一起。你可以把通信数据先读到缓冲区,再通过统一的变量交给工艺逻辑使用,这样后续更换设备或调整通信协议时,工艺逻辑不用大改。
6. 实战案例:三菱 PLC 读取和写入变频器频率
下面用一个非常常见的场景来演示框架中的通信模块怎么写:三菱 PLC 通过 RS485 与变频器通信,实现频率的读取和写入。
这个案例非常适合初学者理解“通信模块如何嵌入到整个 PLC 编程框架中”。
6.1 场景说明
假设使用三菱 FX3U 系列 PLC,搭配一台支持 Modbus RTU 协议的变频器。PLC 需要做到两件事:
- 把目标频率写入变频器;
- 读取变频器当前运行频率。
6.2 通信参数初始化
在三菱 FX 系列中,RS485 通信需要用特殊数据寄存器设置参数,常用的是D8120。它用来设置波特率、数据位、校验位、停止位等。
比如设置为 9600 波特率、8 数据位、无校验、1 停止位,可以把D8120写成H0081或H0C81,不同型号处理方式略有不同。这里先给出初始化片段:
// 通信参数初始化,实际值需根据变频器和PLC型号调整 MOV H0081 D8120; SET M8161; // 三菱 FX 系列 8位数据处理模式,视型号而定请注意:不同 PLC 型号对D8120的定义不完全一样,具体要以你使用的硬件手册为准。
6.3 读取当前频率
三菱 FX 系列常用RS指令进行串行通信。RS指令会指定发送数据区和接收数据区的软元件地址。
读取频率的流程是:
- 组装读命令报文;
- 触发发送请求;
- 等待接收完成;
- 解析接收数据,写入当前频率变量。
核心梯形图逻辑可以用下面的形式理解:
读取频率请求触发 → 把读命令写入发送缓冲区 → RS 指令发送到变频器 → 变频器返回数据 → 解析当前频率在结构化文本中,可以这样描述框架思路:
IF #读频率定时器到 THEN // 组装读取报文:地址 + 功能码 + 寄存器地址 + 数据长度 + 校验 // 具体数值根据变频器协议定义 #发送缓冲[0] := #从站地址; #发送缓冲[1] := #读功能码; #发送缓冲[2] := #寄存器地址高位; #发送缓冲[3] := #寄存器地址低位; // 启动发送并等待接收完成 RS #发送缓冲 D200 K10 D210; END_IF;收到返回数据后,把 D210 开始的寄存器数据解析到#当前频率变量中:
#当前频率 := #接收缓冲[2] * 256 + #接收缓冲[3];这里的D200、D210都只是示例地址,实际项目中建议单独划分通信缓冲区。
6.4 写入目标频率
写入频率和读取频率逻辑类似,区别在于报文内容不同。
IF #写频率请求 THEN // 组装写报文 #发送缓冲[0] := #从站地址; #发送缓冲[1] := #写功能码; #发送缓冲[2] := #寄存器地址高位; #发送缓冲[3] := #寄存器地址低位; #发送缓冲[4] := 目标频率高位; #发送缓冲[5] := 目标频率低位; // 发送并等待响应 RS #发送缓冲 D200 K10 D210; END_IF;6.5 在框架中管理通信状态
通信不能一直无间隔发送,否则会占用 PLC 扫描周期,还可能干扰其他逻辑。
框架中建议加入“通信轮询时间”和“通信超时判断”:
// 每 100ms 触发一次通信请求 IF #通信定时器 >= 100 THEN #通信定时器 := 0; #读频率请求 := TRUE; END_IF; // 如果通信超过 500ms 没有完成,判定超时 IF #通信超时定时器 >= 500 THEN #通信超时报警 := TRUE; END_IF;这样一个标准的通信模块就可以嵌入到主控制层的“通信处理区”中了。
7. 常见问题与排查思路
拿到一套框架之后,实际应用中仍然会遇到不少问题。下面我整理了几个高频故障现象和排查思路,方便你对照定位。
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
| 手动切换到自动时输出瞬间抖动 | 切换瞬间没有封锁输出,或模式判断不严密 | 在模式字切换前先封锁输出,等继电器稳定后再允许输出 |
| 自动流程卡在中间某一步 | 状态机跳转条件不满足,或到位信号丢失 | 查看当前步号,检查传感器反馈和中间变量是否正常,必要时可以手动赋值步号跳转调试 |
| 变频器通信偶尔超时 | 通信线干扰严重,或通信轮询间隔太短 | 检查屏蔽线接地和设备接线,增大通信轮询间隔,增加超时重试次数 |
| 报警复位后设备马上又报警 | 报警条件本身仍然存在,或复位逻辑误把触发条件当成复位信号 | 确认触发条件是否真正消失,复位按钮只复位报警标志,不强制复位设备状态 |
| 程序条数多,下载后部分功能不执行 | 内存占用过大,或程序块调用顺序不合理 | 查看内存使用情况,把非实时性任务放入子程序或使用定时中断执行 |
排查这些问题的核心思路只有一个:先把“状态”和“动作”分开看。状态是条件,动作是结果。很多报警和误动作问题,本质上都是状态判断错了。
8. 最佳实践与工程建议
8.1 命名规范要统一
不管是三菱的注释、西门子的符号名,还是 HMI 里的变量名,尽量使用统一的规则。
比如:
- 按钮类:
BTN_Start、BTN_Stop; - 传感器类:
SNS_气缸1原位、SNS_气缸2到位; - 输出类:
Y_电机正转、Y_电磁阀1; - 报警类:
AL_电机过载、AL_气压不足。
注意命名是给人看的,不是给 PLC 看的,所以一定要清晰、可读。
8.2 数据块和变量表用起来
西门子 PLC 强烈建议使用 DB 块和 PLC 变量表,三菱 PLC 可以使用注释和全局标签。
用变量表强制管理地址,可以避免程序中直接出现大量神秘数字。
8.3 安全逻辑必须独立
急停回路、安全门、光栅等安全相关的输入,不建议和普通工艺逻辑混在一起。即使已经使用安全继电器,在 PLC 程序里也要独立处理,并且在项目调试前做好安全回路验证。
安全类程序的修改,必须在停机状态下进行,并且最好留出独立的版本记录。
8.4 先仿真,再上机
现在大部分 PLC 软件都支持仿真,比如西门子 PLCSIM、三菱 GX Works 的仿真功能。强烈建议先通过仿真验证模式切换、状态跳转、报警复位等核心逻辑,再上真机调试。
上机调试前,一定要把关键输出先断开或者强制,避免误动作造成设备损坏或人员受伤。
8.5 每个项目都要备份程序
程序版本管理不是软件开发的专利。PLC 程序同样需要做版本管理。简单的方式是:
- 项目文件按“日期_设备名_版本号”命名;
- 每次修改前备份一份;
- 保留现场最稳定版本的存档,防止后面改乱。
9. 总结与进阶方向
到这里,我已经把一套通用 PLC 编程框架从概念到实战完整讲了一遍。这套框架并不复杂,重点在于让程序有层次、有边界、可复用。
你可以在日常项目中尝试这样做:
- 把主程序改成基本的“初始化 + 输入刷新 + 模式调度 + 控制逻辑 + 报警处理 + 输出刷新 + 通信处理”结构;
- 把手动、自动、回原点拆分成不同的模块;
- 用数据区或数据块统一管理变量;
- 把通信逻辑独立封装,减少和工艺逻辑的耦合;
- 把报警逻辑集中到专门模块中。
学会这一套框架之后,你的 PLC 编程水平会有一个非常明显的提升,但这不是终点。接下来你还可以继续深入这几个方向:
- 学习更多 IEC 61131-3 语言,尤其是结构化文本 ST,它是表达复杂逻辑的好工具;
- 研究运动控制,比如 PLC 控制伺服电机和步进电机的轨迹规划,常见的伺服电机控制程序都可以用框架来组织;
- 了解工业通信协议,比如 Modbus RTU、Modbus TCP、PROFINET,这些知识会让你的通信模块更灵活;
- 尝试和上位机配合,比如通过 C# 对西门子 PLC 进行数据采集,或者接入 MES 系统。
PLC 编程水平的提升,从来不在于指令背得多熟,而在于你如何组织程序、如何管理状态、如何应对复杂设备的控制需求。希望这套框架能成为你项目里的一把利器,真正帮你把编程整体水平带上一个台阶。
如果你在实际项目中遇到了框架落地方面的问题,也欢迎把现象和思路整理出来。编程这件事,多总结、多交流,进步才会更快。