ST怎么写指针取地址:pPointer := ADR(Variable);

发布于 2026-03-15 11:16:31 · 浏览 4 次 · 评论 0 条

在结构化文本(ST)编程中,ADR() 是获取变量地址的核心指令,pPointer := ADR(Variable); 这一行代码看似简单,却是实现动态数据访问、数组遍历、函数块参数传递、硬件寄存器映射等高级功能的基石。它不依赖于具体PLC品牌(如西门子SCL、倍福TwinCAT、施耐德IEC 61131-3 ST、罗克韦尔Structured Text),因为 ADR 是IEC 61131-3标准明确定义的地址运算符(Address Operator),其行为具有跨平台一致性。下面以“可直接上手执行”的方式,分步讲清:它到底在做什么、为什么必须这样写、常见错误如何排除、哪些场景离不开它,以及如何安全扩展使用。


一、先理解本质:什么是“地址”?什么又是“指针”?

在自动化控制中,所有变量(如 MotorSpeed : INT := 1200;)都存储在PLC内存的某个物理位置,例如起始地址为 DB1.DBW4(西门子)或 %MW100(施耐德)。这个位置编号就是地址——相当于快递收货的“门牌号”。

指针(Pointer)本身是一个特殊变量,它的值不是数据(如1200),而是另一个变量的地址(如 DB1.DBW4 对应的数值 1004)。指针变量的数据类型必须声明为指针类型,例如:

pSpeed : POINTER TO INT;

这行声明表示:pSpeed 是一个能存放 INT 类型变量地址的容器,它自己占4字节(32位系统)或8字节(64位系统),但里面存的不是速度值,是“速度值住在哪”。

ADR(Variable) 就是把 Variable 的门牌号算出来,并交给指针变量。

所以 pPointer := ADR(Variable); 的真实含义是:
Variable 在内存中的精确起始地址读取出来,并存进 pPointer 这个指针变量里。

注意:ADR 不是函数调用,不执行任何计算,也不改变 Variable 的值;它只是编译器在编译阶段就确定好的“地址常量提取操作”。


二、正确书写 ADR() 的5个硬性规则(缺一不可)

以下每条都对应实际工程中高频报错原因:

  1. Variable 必须是已声明、具名、非临时的变量
    ❌ 错误示例:

    p := ADR(123);                    // 字面量无地址
    p := ADR(MotorSpeed + 10);        // 表达式结果无固定地址
    p := ADR(ARRAY[0..9] OF INT);     // 类型声明不是变量

    ✅ 正确写法:

    VAR
        MotorSpeed : INT := 1200;
        StatusFlags : ARRAY[0..7] OF BOOL;
        ConfigData : STRUCT
            MaxCurrent : REAL;
            TimeoutMS : TIME;
        END_STRUCT;
    END_VAR
    
    pSpeed := ADR(MotorSpeed);           // ✔️ 单个变量
    pFlags := ADR(StatusFlags);          // ✔️ 整个数组(取首元素地址)
    pConfig := ADR(ConfigData);          // ✔️ 结构体(取首成员地址)
  2. 指针变量类型必须与目标变量类型严格匹配(TO 后类型一致)
    POINTER TO INT 只能指向 INTDINT 等整数类型变量(因对齐和访问长度需兼容);不能指向 REALSTRING
    若需通用访问,必须用 VOID POINTER(IEC 61131-3 标准支持),但需配合类型转换(见后文“安全扩展”部分)。

  3. ADR() 内部不能加括号嵌套或运算符
    ❌ 错误:

    p := ADR((MotorSpeed));      // 多余括号 → 编译失败
    p := ADR(MotorSpeed[0]);    // 数组索引 → 指向单个元素才合法,但写法错误

    ✅ 正确(若要取数组第3个元素地址):

    p := ADR(StatusFlags[2]);  // 下标从0开始,取第3个BOOL地址
  4. 全局/局部作用域不影响 ADR,但影响指针有效性
    Variable 是 FB(功能块)的 VAR_INPUT,且未在调用时传入实参,则 ADR(Variable) 返回空地址(NULL),解引用会触发运行时错误。务必确保变量已初始化并驻留在有效内存区。

  5. 常量(CONST)不能取地址
    IEC 61131-3 明确规定:ADR() 仅适用于变量(VAR, VAR_GLOBAL, VAR_IN_OUT 等),不适用于 CONSTVAR_ACCESS 声明的符号。
    CONST MaxVal : INT := 100; p := ADR(MaxVal); → 编译报错。


三、三类典型应用场景(附可复制代码)

场景1:动态遍历数组(替代 FOR 循环硬编码)

问题:需对 SensorValues : ARRAY[0..15] OF REAL 的前 n 个元素求平均,但 n 由HMI输入实时变化。

传统做法需循环索引;用指针可消除索引变量,直接按地址偏移访问:

VAR
    SensorValues : ARRAY[0..15] OF REAL := [0.0, 1.2, 2.3, ...];
    nSamples : INT := 8;
    pStart : POINTER TO REAL;
    sum : REAL := 0.0;
    i : INT;
END_VAR

// 1. 获取数组首地址
pStart := ADR(SensorValues);

// 2. 按字节偏移遍历(REAL 占 4 字节)
FOR i := 0 TO nSamples - 1 DO
    sum := sum + pStart^[i];  // pStart^[i] = *(pStart + i * SIZEOF(REAL))
END_FOR

Average := sum / nSamples;

注:pStart^[i] 是标准解引用语法,等价于 C 语言 *(pStart + i)。编译器自动按 TO REAL 类型计算偏移量(i * 4 字节)。

场景2:将多个同类型变量打包传入函数块

问题:需将 Temp1, Temp2, Temp3(均为 REAL)一次性送入自定义滤波FB,避免写3个独立输入引脚。

// 自定义FB声明(FilterFB)
FUNCTION_BLOCK FilterFB
VAR_INPUT
    pSource : POINTER TO REAL;  // 接收首地址
    nPoints : INT;              // 数据点数
END_VAR
VAR
    buffer : ARRAY[0..99] OF REAL;
    i : INT;
END_VAR
// 内部逻辑:从 pSource 读取 nPoints 个 REAL 并滤波
FOR i := 0 TO nPoints - 1 DO
    buffer[i] := pSource^[i] * 0.9 + (1.0 - 0.9) * buffer[i];
END_FOR

// 调用处
VAR
    Temp1, Temp2, Temp3 : REAL;
    temps : ARRAY[0..2] OF REAL;
    pTemps : POINTER TO REAL;
END_VAR

// 构建临时数组并取地址
temps[0] := Temp1; temps[1] := Temp2; temps[2] := Temp3;
pTemps := ADR(temps);
FilterFB(pSource := pTemps, nPoints := 3);

场景3:访问硬件I/O映射地址(底层控制必需)

当PLC需要直接读写特定物理地址(如模拟量模块的配置寄存器),且该地址未被符号化时:

// 假设某模块基地址为 0x80000000(32位地址)
VAR
    pHWReg : POINTER TO DWORD;
    configValue : DWORD;
END_VAR

pHWReg := ADR(DWORD#16#80000000);  // ⚠️ 注意:此写法仅在支持地址字面量的平台有效(如TwinCAT)
// 更通用写法(推荐):
// 声明一个 dummy 变量强制分配到目标地址(通过 AT 属性)
VAR_GLOBAL
    HW_CONFIG_REG AT %MD0 : DWORD;  // 强制映射到 %MD0(具体地址依硬件手册)
END_VAR
pHWReg := ADR(HW_CONFIG_REG);  // 安全获取

// 读取配置
configValue := pHWReg^;

// 写入使能位(bit 0)
pHWReg^ := pHWReg^ OR 16#1;

四、必须规避的5个高危错误(附诊断方法)

错误现象 根本原因 快速诊断法
编译报错 “ADR operand not allowed” CONST、表达式、未声明变量使用 ADR 检查 ADR() 内是否为纯变量名,右键变量名→查看声明位置
运行时崩溃或随机值 指针类型与目标类型不匹配(如 POINTER TO INT 指向 REAL 查看指针声明的 TO 类型,对比目标变量类型;启用编译器“类型严格检查”
p^ 返回0或旧值 指针未初始化(p := 0;)或 ADR() 目标变量未分配内存(如FB未实例化) 在线监控指针变量值:若为 0NULL,说明 ADR 未成功赋值
数组越界无提示 p^[i]i 超出数组范围,但编译器不检查 手动添加边界判断:IF i < SIZEOF(Array)/SIZEOF(Element) THEN ... END_IF
多任务中数据错乱 指针指向全局变量,但被多个任务并发读写 改用 VAR_IN_OUT 传参,或对共享内存加 CRITICAL_SECTION

五、安全扩展:VOID POINTER 与类型转换

当需一个指针处理多种类型(如同时读 INTREAL),用 VOID POINTER

VAR
    pVoid : POINTER TO VOID;
    myInt : INT := 42;
    myReal : REAL := 3.14;
END_VAR

pVoid := ADR(myInt);
// 转换为 INT 指针后解引用
INT_PTR : POINTER TO INT := pVoid;
valueInt := INT_PTR^;  // 42

// 转换为 REAL 指针(⚠️ 确保内存内容确实是 REAL 格式!)
REAL_PTR : POINTER TO REAL := pVoid;
valueReal := REAL_PTR^;  // 危险!此时读的是 INT 的二进制解释为 REAL → 得到乱码

✅ 安全用法:仅用于相同内存布局的类型间转换(如 INTWORDBYTE 数组),或配合 MOVE 指令做字节级搬运。


六、不同平台语法微调对照表

平台 ADR 是否支持 指针声明示例 解引用符号 备注
TwinCAT 3 (Beckhoff) ✔️ 全面支持 p : POINTER TO LREAL; p^ 支持 ADR() 直接作用于 AT 地址变量
博途 SCL (Siemens) ✔️ pSpeed : P#INT; pSpeed^ P# 是西门子指针关键字,ADR() 用法完全一致
Unity Pro (Schneider) ✔️ p : POINTER TO DINT; p^ 需在“项目设置”启用指针功能
Logix Designer (Rockwell) ❌ 不支持 ADR() 使用 @ 符号(如 @MyTag @MyTag 非IEC标准,此处不展开

所有符合 IEC 61131-3 的平台,pPointer := ADR(Variable); 的语法和语义完全一致。


七、性能真相:ADR() 几乎零开销

ADR()编译期完成,生成的机器码仅为一条地址加载指令(如 x86 的 LEA EAX, [Variable]),不消耗扫描周期时间。指针解引用 p^ 也仅比直接访问多1个内存寻址周期,在现代PLC中可忽略不计。真正影响性能的是不当的指针链式访问(如 p1^.[0].field.p2^.[5]^),应尽量扁平化。


八、调试技巧:3步定位指针问题

  1. 静态检查:编译前,将鼠标悬停在 ADR(Variable) 上,IDE 应显示 Variable 的绝对地址(如 DB1.DBX0.0);
  2. 在线监控:运行时观察指针变量值,确认是否为非零有效地址(如 16#8000A000);
  3. 交叉验证:用 MOVE 指令将 p^ 的值复制到中间变量,再与原始变量值比对是否一致。

pPointer := ADR(Variable); 不是炫技语法,而是让ST具备C语言级内存操控能力的关键开关。掌握它,意味着你能跳出“变量名直连”的思维定式,进入基于地址的灵活架构设计——从优化数据搬运,到对接第三方驱动,再到实现自定义通信协议,皆以此为起点。

评论 (0)

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

扫一扫,手机查看

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