西门子 S7-1200/1500 系列 PLC 的间接寻址与指针应用,是突破固定编程思维、实现灵活数据处理的核心技术。掌握这项技术,能让你用同一段程序处理成百上千个数据点,大幅减少重复代码。
第一部分:理解间接寻址的本质
1.1 直接寻址的局限
传统编程中,你写的是 DB1.DBW10 或 MW100 这样的绝对地址。每个数据点对应一行代码,100 个温度点就要写 100 次赋值。维护时改一个地址,可能牵一发而动全身。
间接寻址的核心思想是:把地址本身也变成数据,运行时动态计算目标位置。
1.2 指针的本质
西门子 PLC 中的指针,本质上是一个 32 位或 48 位的存储区域,里面存放着目标数据的精确坐标。你可以把它理解为"地址的地址"——指针变量存的不是温度值,而是"温度值存在哪里"这个信息。
第二部分:S7-1200/1500 的寻址方式详解
2.1 AREA 间接寻址(最简单)
这种方式通过改变地址的"偏移量"来指向不同位置,适合有规律的连续数据区。
语法格式:DB1.DBW[索引] 或 MW[索引]
操作示例:
假设 DB1 中定义了 Array[0..99] of Int 类型的温度数组,索引号为 0 到 99。
- 创建 一个 Int 类型的变量
Index作为索引指针 - 写入 索引值:在程序中将
Index := 5 - 访问 元素:
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 |
| 编程便捷性 | 需手动组装 | 直接传递变量名即可 |
构造指针的实操步骤:
-
声明 指针变量:
// 在 FB 的 Static 区 #PtrSource : DB_ANY; // 指向源数据块 #OffsetByte : DInt; // 字节偏移量 #MyVariant : VARIANT; // 通用数据引用 -
赋值 指针:
// 方法1:直接指向变量(推荐) #MyVariant := #LocalArray; // 方法2:动态计算偏移(高级) // 使用 AT 覆盖或 Slice 访问 -
使用 系统指令解析指针:
// 读取 VARIANT 指向的数据类型信息 TypeOf(#MyVariant); // 返回数据类型 TypeOfElements(#MyVariant); // 返回数组元素类型 CountOfElements(#MyVariant); // 返回数组元素个数
2.3 DB_ANY 与数据块动态访问
DB_ANY 类型是 S7-1200/1500 的利器,让程序在运行时才决定操作哪个数据块。
完整操作流程:
-
创建 多个结构相同的数据块(如
Recipe_DB_1,Recipe_DB_2...),或使用数组化 DB -
声明
DB_ANY变量:// 在 FB 的 Input 区 #SelectedRecipe : DB_ANY; // 由 HMI 或程序赋值 -
转换 并访问:
// 将 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_1 到 Recipe_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 支持更优雅的 Slice 和 AT 覆盖:
// 在优化访问的 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 次动态寻址时需警惕。
优化策略:
- 批量缓冲:用
PEEK_BLK/POKE_BLK一次性搬运整个数据块,再本地解析 - 预处理索引:在循环外计算好所有地址,循环内只做赋值
- 避免 VARIANT 嵌套:多层
VARIANT转换会指数级增加处理时间
第五部分:诊断与调试
5.1 监控指针值的技巧
在 TIA Portal 监控表中:
- 添加 指针变量(如
#MyPointer) - 右键 → 表示方式 → 指针(或十六进制)
- 解析 格式:
16#8401_0064_0000_0000表示:84= DB 区01= DB 编号 10064= 字节偏移 100- 后续为位偏移(通常 0)
5.2 错误代码速查
| 错误代码 | 含义 | 排查方向 |
|---|---|---|
16#8090 |
无效 DB 编号 | 检查 DB_ANY 赋值 |
16#8091 |
区域长度错误 | 偏移量超出 DB 实际大小 |
16#8092 |
区域类型错误 | area 参数写错(如把 16#84 写成 16#48) |
16#80A1 |
写保护冲突 | 试图写入只读 DB 或系统区域 |

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