Skip to content

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 定时器]───

               [子程序返回]───

本节小结

子程序与功能块要点:

┌────────────────────────────────────────────────────┐
│  子程序                                            │
│  · 被调用时执行,执行完返回                       │
│  · 可带参数传递                                   │
│  · 调用指令 / 返回指令                            │
├────────────────────────────────────────────────────┤
│  功能(无状态)                                    │
│  · 无背景数据块,无状态保持                       │
│  · 适合纯计算、数据处理                           │
│  · 每次调用独立                                   │
├────────────────────────────────────────────────────┤
│  功能块(有状态)                                  │
│  · 有背景数据块,有状态保持                       │
│  · 可多次实例化                                   │
│  · 适合设备控制等有状态场景                       │
├────────────────────────────────────────────────────┤
│  设计原则                                          │
│  · 单一职责:每个模块做一件事                     │
│  · 高内聚低耦合                                   │
│  · 接口清晰,参数明确                             │
└────────────────────────────────────────────────────┘

练习题

  1. 说明功能(无状态)和功能块(有状态)的主要区别。
  2. 编写一个子程序实现两数相加。
  3. 设计一个电机控制功能块,包含启停和故障检测。
  4. 什么场景适合使用功能块(有状态)而不是功能(无状态)?

← 上一节:4.3 程序控制指令 | 返回目录 | 下一节:4.5 中断程序设计 →

本教程由 AI (Claude Opus 4.5) 生成,仅供学习参考