4.4 子程序与功能块
本节将介绍PLC的模块化编程方法
学习目标
- 掌握子程序的创建和调用
- 理解功能块的概念
- 能够实现模块化编程
1. 模块化编程概述
1.1 什么是模块化编程
模块化编程是将程序分解为多个独立的功能模块,每个模块完成特定功能,通过调用和参数传递协同工作。
模块化编程结构:
┌─────────────────────────────────────────────────────┐
│ 主程序 (OB1/Main) │
├─────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │电机控制 │ │温度控制 │ │报警处理 │ │
│ │ FB1 │ │ FB2 │ │ FC1 │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 电机1 │ │ 加热器 │ │ 声光报警 │ │
│ │ DB1 │ │ DB2 │ │ (无DB) │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
└─────────────────────────────────────────────────────┘1.2 模块化编程优点
| 优点 | 说明 |
|---|---|
| 复用性 | 相同功能只需编写一次 |
| 可维护性 | 修改局部不影响整体 |
| 可读性 | 程序结构清晰 |
| 团队协作 | 可分工并行开发 |
| 调试方便 | 可单独测试各模块 |
1.3 程序块类型
常见程序块类型:
┌────────────────────────────────────────────────────┐
│ 块类型 │ 说明 │ 特点 │
├────────────────────────────────────────────────────┤
│ 主程序 │ 组织块 │ 系统调用入口 │
│ 功能块 │ 带状态的模块 │ 有数据块,有状态 │
│ 功能 │ 无状态的模块 │ 无数据块,无状态 │
│ 数据块 │ 数据存储 │ 存储数据 │
│ 系统块 │ 系统功能块/功能 │ 系统内置 │
└────────────────────────────────────────────────────┘
通用程序结构:
· 主程序:主扫描程序
· 子程序:子程序模块
· 中断程序:中断服务程序2. 子程序基础
2.1 子程序概念
子程序工作流程:
主程序: 子程序:
┌─────────────┐ ┌─────────────┐
│ ... │ │ 子程序代码 │
├─────────────┤ │ │
│ CALL 子程序 │────────────→ ... │
├─────────────┤ │ │
│ (暂停) │ │ RETURN │
├─────────────┤ ←───────│ │
│ 继续执行 │ └─────────────┘
└─────────────┘
特点:
· 被调用时执行
· 执行完返回主程序
· 可被多次调用
· 可带参数2.2 子程序示例
【子程序结构】
主程序:
启动按钮
───┤├───────[CALL 子程序1]─── // 调用子程序1
选择开关
───┤├───────[CALL 子程序2]─── // 调用子程序2
[主程序结束]─── // 主程序结束
【子程序1】
条件1
───┤├───────( 输出1 )───
[子程序返回]─── // 子程序返回
【子程序2】
条件2
───┤├───────( 输出2 )───
[子程序返回]─── // 子程序返回
[程序结束]─── // 程序结束2.3 带参数的子程序
带参数子程序调用:
【调用】
启动条件
───┤├───────[CALL 子程序1 参数1 参数2 结果]───
· 子程序1:子程序标识
· 参数1:输入参数1
· 参数2:输入参数2
· 结果:输出参数
【子程序定义】
【子程序1】
常ON条件
───┤├───────[ADD 参数1 参数2 结果]─── // 两数相加
[子程序返回]───
【说明】
参数按顺序对应
参数1→第一参数,参数2→第二参数,结果→第三参数3. 功能模块(无状态)
3.1 功能模块基本概念
功能模块(Function)是没有内部存储的程序块,每次调用独立执行。
功能模块特点:
┌────────────────────────────────────────────────────┐
│ 功能模块特性 │
├────────────────────────────────────────────────────┤
│ · 无背景数据块 │
│ · 无内部状态保持 │
│ · 每次调用独立 │
│ · 适合纯计算、转换 │
│ · 输入/输出/临时变量 │
└────────────────────────────────────────────────────┘3.2 功能模块创建与编程
功能模块接口定义(逻辑表达):
功能 加法运算 : 整数
// 函数名和返回值类型
输入变量
数值1 : 整数; // 输入参数1
数值2 : 整数; // 输入参数2
结束
输出变量
结果 : 整数; // 输出参数
结束
临时变量
临时值 : 整数; // 临时变量
结束
开始
// 函数体
临时值 := 数值1 + 数值2;
结果 := 临时值;
加法运算 := 临时值; // 返回值
结束功能3.3 功能模块调用
【梯形图调用】
┌──────────┐
启动 ─────┤ 加法运算 │
│ │
数据1 ────┤数值1 │
数据2 ────┤数值2 │
│ │
│结果 ─────├──── 输出数据
└──────────┘
【逻辑表达调用】
输出数据 := 加法运算(数值1 := 数据1, 数值2 := 数据2);
// 或
加法运算(数值1 := 数据1,
数值2 := 数据2,
结果 => 输出数据);4. 功能块(有状态)
4.1 功能块基本概念
功能块(Function Block)有背景数据块,可以保存内部状态。
功能块特点:
┌────────────────────────────────────────────────────┐
│ 功能块特性 │
├────────────────────────────────────────────────────┤
│ · 有背景数据块(实例数据块) │
│ · 内部变量可保持状态 │
│ · 同一功能块可多次实例化 │
│ · 适合有状态的控制对象 │
│ · 如:电机控制、阀门控制 │
└────────────────────────────────────────────────────┘
功能块与实例数据块关系:
┌─────────┐ ┌─────────┐
│功能块 │ ←──→ │ 数据块1 │ 实例1
│电机控制 │ │ 电机1 │
└─────────┘ └─────────┘
│ ┌─────────┐
└────────→ │ 数据块2 │ 实例2
│ 电机2 │
└─────────┘4.2 功能块创建与编程
功能块接口定义(逻辑表达):
功能块 电机控制
// 功能块名称
输入变量
启动信号 : 布尔; // 启动
停止信号 : 布尔; // 停止
结束
输出变量
运行状态 : 布尔; // 运行状态
故障标志 : 布尔; // 故障
结束
静态变量
// 静态变量(保存在实例数据块中)
运行命令 : 布尔; // 运行命令
故障锁存 : 布尔; // 故障锁存
结束
临时变量
// 临时变量
临时值 : 布尔;
结束
开始
// 启停控制
如果 启动信号 且 非 停止信号 则
运行命令 := 真;
否则如果 停止信号 则
运行命令 := 假;
结束如果;
// 输出
运行状态 := 运行命令;
故障标志 := 故障锁存;
结束功能块4.3 功能块调用(实例化)
【梯形图调用】
┌──────────┐
启动按钮 ──┤ 电机控制 │──── 实例数据块
│ │
启动按钮 ──┤启动信号 │
停止按钮 ──┤停止信号 │
│ │
│运行状态 ─├──── 运行指示灯
│故障标志 ─├──── 故障指示灯
└──────────┘
【逻辑表达调用】
// 声明实例
变量
电机1 : 电机控制; // 自动创建实例数据块
结束
// 调用
电机1(启动信号 := 启动按钮,
停止信号 := 停止按钮,
运行状态 => 运行指示灯,
故障标志 => 故障指示灯);
// 访问内部变量
如果 电机1.运行命令 则
// ...
结束如果;4.4 多实例
同一功能块控制多台设备(逻辑表达):
变量
电机1 : 电机控制; // 电机1实例
电机2 : 电机控制; // 电机2实例
电机3 : 电机控制; // 电机3实例
结束
// 电机1
电机1(启动信号 := 启动按钮1, 停止信号 := 停止按钮1,
运行状态 => 运行灯1, 故障标志 => 故障灯1);
// 电机2
电机2(启动信号 := 启动按钮2, 停止信号 := 停止按钮2,
运行状态 => 运行灯2, 故障标志 => 故障灯2);
// 电机3
电机3(启动信号 := 启动按钮3, 停止信号 := 停止按钮3,
运行状态 => 运行灯3, 故障标志 => 故障灯3);
每个实例有独立的状态数据5. 功能与功能块的区别
5.1 对比表
| 特性 | 功能(无状态) | 功能块(有状态) |
|---|---|---|
| 背景数据块 | 无 | 有(实例数据块) |
| 状态保持 | 不保持 | 保持 |
| 实例化 | 不需要 | 需要 |
| 内部变量 | 仅临时变量 | 静态+临时 |
| 适用场景 | 纯计算 | 有状态控制 |
| 调用开销 | 小 | 较大 |
5.2 选择原则
何时使用功能(无状态):
· 数学计算
· 数据转换
· 格式处理
· 无需保存状态
何时使用功能块(有状态):
· 设备控制(电机、阀门)
· 需要保存状态
· 定时器/计数器封装
· 多实例应用6. 参数传递
6.1 参数类型
参数类型说明:
┌────────────────────────────────────────────────────┐
│ 类型 │ 说明 │
├────────────────────────────────────────────────────┤
│ 输入参数 │ 输入参数,只读 │
│ 输出参数 │ 输出参数,只写 │
│ 输入输出 │ 输入输出,可读可写 │
│ 临时变量 │ 临时变量,不保持 │
│ 静态变量 │ 静态变量,保持(仅功能块) │
└────────────────────────────────────────────────────┘6.2 传值与传址
传值(By Value):
· 复制参数值
· 修改不影响原变量
· 适用于简单数据类型
传址(By Reference):
· 传递地址
· 修改影响原变量
· 适用于数组、结构体
· 输入输出参数使用传址
示例(逻辑表达):
输入输出参数
数据数组 : 数组[0..9] 整数; // 传址
结束7. 应用实例
7.1 电机控制功能块
完整的电机控制功能块(逻辑表达):
功能块 电机完整控制
输入变量
启动 : 布尔; // 启动命令
停止 : 布尔; // 停止命令
急停 : 布尔; // 急停
反馈 : 布尔; // 运行反馈
最大运行时间 : 时间 := 1小时; // 最大运行时间
结束
输出变量
控制输出 : 布尔; // 输出到接触器
运行中 : 布尔; // 运行指示
故障 : 布尔; // 故障指示
运行时间 : 时间; // 累计运行时间
结束
静态变量
运行命令 : 布尔; // 运行命令
启动边沿 : 布尔; // 启动边沿
故障锁存 : 布尔; // 故障锁存
反馈定时器 : 通电延时; // 反馈超时定时器
运行计时器 : 通电延时; // 运行计时
结束
开始
// 急停优先
如果 急停 则
运行命令 := 假;
故障锁存 := 真;
// 正常启停
否则如果 启动 且 非 停止 且 非 故障锁存 则
运行命令 := 真;
否则如果 停止 则
运行命令 := 假;
结束如果;
// 反馈检测(启动后3秒内应有反馈)
反馈定时器(输入 := 运行命令 且 非 反馈,
设定值 := 3秒);
如果 反馈定时器.输出 则
故障锁存 := 真;
运行命令 := 假;
结束如果;
// 运行计时
运行计时器(输入 := 运行中, 设定值 := 最大运行时间);
运行时间 := 运行计时器.当前值;
// 输出
控制输出 := 运行命令;
运行中 := 反馈;
故障 := 故障锁存;
结束功能块7.2 温度PID功能块
简化的PID功能块(逻辑表达):
功能块 简易PID控制
输入变量
过程值 : 实数; // 过程值
设定值 : 实数; // 设定值
比例系数 : 实数 := 1.0; // 比例系数
积分系数 : 实数 := 0.1; // 积分系数
微分系数 : 实数 := 0.01;// 微分系数
结束
输出变量
控制输出 : 实数; // 控制输出 (0-100%)
结束
静态变量
误差 : 实数; // 误差
上次误差 : 实数; // 上次误差
积分累计 : 实数; // 积分累计
微分值 : 实数; // 微分
结束
开始
// 计算误差
误差 := 设定值 - 过程值;
// 积分
积分累计 := 积分累计 + 误差;
// 积分限幅
如果 积分累计 > 1000 则 积分累计 := 1000; 结束如果;
如果 积分累计 < -1000 则 积分累计 := -1000; 结束如果;
// 微分
微分值 := 误差 - 上次误差;
上次误差 := 误差;
// PID计算
控制输出 := 比例系数 * 误差 + 积分系数 * 积分累计 + 微分系数 * 微分值;
// 输出限幅
如果 控制输出 > 100.0 则 控制输出 := 100.0; 结束如果;
如果 控制输出 < 0.0 则 控制输出 := 0.0; 结束如果;
结束功能块7.3 通用定时器封装
子程序封装延时功能:
【调用】
启动按钮
───┤├───────[CALL 延时子程序 启动条件 延时设定 延时输出]───
· 启动条件:启动条件标志
· 延时设定:延时时间(5秒)
· 延时输出:延时到达输出
【延时子程序】
// 输入 启动条件 = 启动条件(参数1)
// 输入 延时设定 = 设定时间(参数2)
// 输出 延时输出 = 到达标志(参数3)
参数1 参数2
───┤├───────[OUT 定时器]───
定时器触点
───┤├───────[SET 参数3]───
参数1
───┤/├──────[RST 参数3]───
[RST 定时器]───
[子程序返回]───本节小结
子程序与功能块要点:
┌────────────────────────────────────────────────────┐
│ 子程序 │
│ · 被调用时执行,执行完返回 │
│ · 可带参数传递 │
│ · 调用指令 / 返回指令 │
├────────────────────────────────────────────────────┤
│ 功能(无状态) │
│ · 无背景数据块,无状态保持 │
│ · 适合纯计算、数据处理 │
│ · 每次调用独立 │
├────────────────────────────────────────────────────┤
│ 功能块(有状态) │
│ · 有背景数据块,有状态保持 │
│ · 可多次实例化 │
│ · 适合设备控制等有状态场景 │
├────────────────────────────────────────────────────┤
│ 设计原则 │
│ · 单一职责:每个模块做一件事 │
│ · 高内聚低耦合 │
│ · 接口清晰,参数明确 │
└────────────────────────────────────────────────────┘练习题
- 说明功能(无状态)和功能块(有状态)的主要区别。
- 编写一个子程序实现两数相加。
- 设计一个电机控制功能块,包含启停和故障检测。
- 什么场景适合使用功能块(有状态)而不是功能(无状态)?