文章目录

西门子PLC的间接寻址与指针应用

发布于 2026-03-23 10:19:29 · 浏览 5 次 · 评论 0 条

西门子 S7-1200/1500 系列 PLC 的间接寻址与指针应用,是突破固定编程思维、实现灵活数据处理的核心技术。掌握这项技术,能让你用同一段程序处理成百上千个数据点,大幅减少重复代码。


第一部分:理解间接寻址的本质

1.1 直接寻址的局限

传统编程中,你写的是 DB1.DBW10MW100 这样的绝对地址。每个数据点对应一行代码,100 个温度点就要写 100 次赋值。维护时改一个地址,可能牵一发而动全身。

间接寻址的核心思想是:把地址本身也变成数据,运行时动态计算目标位置

1.2 指针的本质

西门子 PLC 中的指针,本质上是一个 32 位或 48 位的存储区域,里面存放着目标数据的精确坐标。你可以把它理解为"地址的地址"——指针变量存的不是温度值,而是"温度值存在哪里"这个信息。


第二部分:S7-1200/1500 的寻址方式详解

2.1 AREA 间接寻址(最简单)

这种方式通过改变地址的"偏移量"来指向不同位置,适合有规律的连续数据区。

语法格式DB1.DBW[索引]MW[索引]

操作示例

假设 DB1 中定义了 Array[0..99] of Int 类型的温度数组,索引号为 0 到 99。

  1. 创建 一个 Int 类型的变量 Index 作为索引指针
  2. 写入 索引值:在程序中将 Index := 5
  3. 访问 元素:DB1.Temperatures[Index] 即指向第 6 个温度值(从0开始计数)

关键规则

数据类型 索引单位 地址增量 示例说明
Byte (字节) 1 +1 DB1.DB[5] 偏移 5 字节
Word (字) 2 +2 DB1.DBW[3] 偏移 6 字节起
DWord (双字) 4 +4 DB1.DBD[2] 偏移 8 字节起
Real (浮点) 4 +4 DB1.DBR[4] 偏移 16 字节起

典型应用场景

批量读取 10 个模拟量输入,存入数组:

// 在循环 OB 或 FC 中
#i := 0;
WHILE #i < 10 DO
    // 将 IW64, IW66, IW68... 依次读入数组
    #TemperatureArray[#i] := IW[64 + (#i * 2)];
    #i := #i + 1;
END_WHILE;

2.2 寄存器间接寻址(更灵活)

通过 P# 前缀构造指针,可以指向任意存储区,包括位、字节、字、双字。

指针格式详解

P#X.Y 其中:

  • X = 字节地址(0 到上限)
  • Y = 位地址(0 到 7,仅对位访问有效)

跨 DB 访问的核心:ANY 类型与 VARIANT 类型

TIA Portal V13 以后,推荐使用 VARIANT 替代旧的 ANY

特性 ANY (S7-300/400/早期1200) VARIANT (S7-1200 V4.0+/1500)
长度 10 字节 可指向更复杂结构
支持数据类型 基本类型为主 支持 UDT、Array、Struct
编程便捷性 需手动组装 直接传递变量名即可

构造指针的实操步骤

  1. 声明 指针变量:

    // 在 FB 的 Static 区
    #PtrSource : DB_ANY;      // 指向源数据块
    #OffsetByte : DInt;       // 字节偏移量
    #MyVariant : VARIANT;     // 通用数据引用
  2. 赋值 指针:

    // 方法1:直接指向变量(推荐)
    #MyVariant := #LocalArray;
    
    // 方法2:动态计算偏移(高级)
    // 使用 AT 覆盖或 Slice 访问
  3. 使用 系统指令解析指针:

    // 读取 VARIANT 指向的数据类型信息
    TypeOf(#MyVariant);        // 返回数据类型
    TypeOfElements(#MyVariant); // 返回数组元素类型
    CountOfElements(#MyVariant); // 返回数组元素个数

2.3 DB_ANY 与数据块动态访问

DB_ANY 类型是 S7-1200/1500 的利器,让程序在运行时才决定操作哪个数据块。

完整操作流程

  1. 创建 多个结构相同的数据块(如 Recipe_DB_1, Recipe_DB_2...),或使用数组化 DB

  2. 声明 DB_ANY 变量:

    // 在 FB 的 Input 区
    #SelectedRecipe : DB_ANY;  // 由 HMI 或程序赋值
  3. 转换 并访问:

    // 将 DB_ANY 转换为具体 DB 的接口
    #TempRecipe := #SelectedRecipe^;  // ^ 符号解引用
    
    // 或使用 PEEK/POKE 指令直接读写
    #TempReal := PEEK_REAL(area := 16#84,  // 16#84 = DB 区
                            dbNumber := DB_ANY_TO_UINT(#SelectedRecipe),
                            byteOffset := 0);

存储区代码速查

代码 含义 示例应用
16#81 输入区 I 读取数字量输入状态
16#82 输出区 Q 强制输出值
16#83 位存储区 M 全局标志位
16#84 数据块 DB 配方、参数表
16#1C 本地数据 L 临时变量(仅限某些指令)

第三部分:实战应用——配方管理系统

3.1 需求分析

某生产线需存储 20 组配方,每组包含:

  • 温度设定值(Real,4 字节)
  • 压力设定值(Real,4 字节)
  • 时间设定值(Time,4 字节)
  • 产品名称(String[20],24 字节)

总数据量:36 字节/组 × 20 组 = 720 字节

3.2 方案设计:单 DB + 偏移寻址 vs 多 DB + DB_ANY

方案 A:连续数组(简单场景)

// 在 Global DB 中定义
TYPE "RecipeData"
STRUCT
    TempSet : Real;
    PressSet : Real;
    TimeSet : Time;
    ProdName : String[20];
END_STRUCT;

// 变量声明
Recipes : Array[1..20] of "RecipeData";

访问方式直接:Recipes[#Index].TempSet

方案 B:分块存储(需独立下载/备份时)

创建 Recipe_1Recipe_20 共 20 个数据块,通过 DB_ANY 动态切换。

3.3 完整实现代码(方案 B)

步骤 1:创建配方数据块模板

新建全局 DB,命名为 Recipe_Template,包含上述结构。然后复制出 20 个实例。

步骤 2:编写配方管理 FB

FUNCTION_BLOCK "FB_RecipeManager"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1

VAR_INPUT
    RecipeIndex : Int;           // 1 ~ 20,选择配方编号
    Command : Byte;              // 1=读取, 2=写入, 3=复制
    SourceData : "RecipeData";   // 写入时的源数据
END_VAR

VAR_OUTPUT
    TargetData : "RecipeData";   // 读取结果
    Done : Bool;
    Error : Bool;
    ErrorID : Word;
END_VAR

VAR
    dbNumber : UInt;
    currentDB : DB_ANY;
    tempVariant : VARIANT;
    i : Int;

    // 配方 DB 的编号列表(需根据实际项目填写)
    RecipeDBList : Array[1..20] of UInt := [
        101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
        111, 112, 113, 114, 115, 116, 117, 118, 119, 120
    ];
END_VAR

BEGIN
    // 有效性检查
    IF #RecipeIndex < 1 OR #RecipeIndex > 20 THEN
        #Error := TRUE;
        #ErrorID := 16#8001;  // 索引越界
        RETURN;
    END_IF;

    // 获取目标 DB 编号
    #dbNumber := #RecipeDBList[#RecipeIndex];

    // 构造 DB_ANY(S7-1500 支持 UINT_TO_DB_ANY)
    #currentDB := UINT_TO_DB_ANY(#dbNumber);

    // 执行命令
    CASE #Command OF
        1: // 读取配方
            BEGIN
                // 将 DB_ANY 转换为 VARIANT 可读形式
                #tempVariant := #currentDB;

                // 使用 FieldRead 指令或解引用
                // S7-1500 推荐方式:直接通过符号访问
                // 注意:实际项目中需用 UNREF 或类似机制
                // 此处展示 PEEK 方式读取 Real 类型
                #TargetData.TempSet := PEEK_REAL(area := 16#84,
                                                  dbNumber := #dbNumber,
                                                  byteOffset := 0);
                #TargetData.PressSet := PEEK_REAL(area := 16#84,
                                                   dbNumber := #dbNumber,
                                                   byteOffset := 4);
                // Time 类型需特殊处理,或转为 DInt 存储
                #TargetData.TimeSet := DINT_TO_TIME(
                    PEEK_DINT(area := 16#84,
                              dbNumber := #dbNumber,
                              byteOffset := 8));

                #Done := TRUE;
            END;

        2: // 写入配方
            BEGIN
                POKE(area := 16#84,
                     dbNumber := #dbNumber,
                     byteOffset := 0,
                     value := #SourceData.TempSet);
                POKE(area := 16#84,
                     dbNumber := #dbNumber,
                     byteOffset := 4,
                     value := #SourceData.PressSet);
                POKE(area := 16#84,
                     dbNumber := #dbNumber,
                     byteOffset := 8,
                     value := TIME_TO_DINT(#SourceData.TimeSet));

                #Done := TRUE;
            END;

        3: // 复制配方(源Index在另一个输入中,此处简化)
            // 实际实现需额外参数
            ;

    ELSE
        #Error := TRUE;
        #ErrorID := 16#8002;  // 无效命令
    END_CASE;

END_FUNCTION_BLOCK

步骤 3:优化版本——使用 Slice 访问(S7-1500)

S7-1500 支持更优雅的 SliceAT 覆盖:

// 在优化访问的 DB 中,可将 Any 类型数据作为整体搬运
VAR_TEMP
    RawBytes : Array[0..35] of Byte;  // 36 字节覆盖整个结构
    RecipeOverlay AT RawBytes : "RecipeData";
END_VAR

// 使用 POKE_BLK 批量读写
POKE_BLK(area_src := 16#84,
         dbNumber_src := #dbNumber,
         byteOffset_src := 0,
         area_dest := 16#83,  // 临时 M 区
         dbNumber_dest := 0,
         byteOffset_dest := #TempBufferAddr,
         count := 36);

// 再解析到结构体

第四部分:高级技巧与陷阱规避

4.1 动态数组遍历的边界处理

常见错误:索引超范围导致 CPU 停机

防御性编程模板

// 循环前强制限定范围
#SafeIndex := LIMIT(MN := 0, IN := #RequestedIndex, MX := #MaxIndex);

// 或使用条件判断
IF #RequestedIndex > #MaxIndex THEN
    #RequestedIndex := #MaxIndex;
ELSIF #RequestedIndex < 0 THEN
    #RequestedIndex := 0;
END_IF;

4.2 指针失效场景

以下情况指针会"失效"或指向错误位置:

危险操作 后果 解决方案
DB 被重新下载 符号地址变化 使用非优化访问或固定结构
在线修改 DB 偏移量改变 重启 CPU 或重新初始化指针
跨项目复制 DB 编号不同 使用 DB_ANY 动态绑定
数组越界访问 读取相邻数据或故障 严格边界检查

4.3 性能优化要点

间接寻址比直接寻址慢 3~10 倍,在 100ms 周期内处理超过 1000 次动态寻址时需警惕。

优化策略

  1. 批量缓冲:用 PEEK_BLK/POKE_BLK 一次性搬运整个数据块,再本地解析
  2. 预处理索引:在循环外计算好所有地址,循环内只做赋值
  3. 避免 VARIANT 嵌套:多层 VARIANT 转换会指数级增加处理时间

第五部分:诊断与调试

5.1 监控指针值的技巧

在 TIA Portal 监控表中:

  1. 添加 指针变量(如 #MyPointer
  2. 右键 → 表示方式 → 指针(或十六进制)
  3. 解析 格式:16#8401_0064_0000_0000 表示:
    • 84 = DB 区
    • 01 = DB 编号 1
    • 0064 = 字节偏移 100
    • 后续为位偏移(通常 0)

5.2 错误代码速查

错误代码 含义 排查方向
16#8090 无效 DB 编号 检查 DB_ANY 赋值
16#8091 区域长度错误 偏移量超出 DB 实际大小
16#8092 区域类型错误 area 参数写错(如把 16#84 写成 16#48)
16#80A1 写保护冲突 试图写入只读 DB 或系统区域

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文