在结构化文本(Structured Text,ST)编程中,访问结构体成员是自动化控制逻辑中最基础、最频繁的操作之一。以 MotorData.Speed := 1500; 这一语句为例,它看似简单,却隐含了数据类型定义、内存布局、作用域规则、编译器解析机制和运行时赋值行为等多重技术细节。下面分步拆解,手把手带你写出正确、安全、可维护的结构体成员访问代码。
一、先定义结构体:没有定义,就没有访问
ST 中所有变量必须显式声明。MotorData.Speed 能被识别,前提是 MotorData 是一个已声明的变量,且其数据类型是一个包含 Speed 成员的结构体(STRUCT)。不能跳过这一步直接写赋值语句,否则编译器报错:“identifier ‘MotorData’ not declared”。
声明结构体类型(在全局或程序组织单元 POUs 的 TYPE 区域中):
TYPE MotorDataType : STRUCT
Speed : INT; // 当前转速,单位 rpm
Torque : REAL; // 输出扭矩,单位 N·m
Status : BYTE; // 状态字节(bit0=运行中, bit1=故障)
IsEnabled : BOOL; // 使能标志
FaultCode : WORD; // 故障码(0=无故障)
END_STRUCT
END_TYPE
✅ 关键点:
TYPE ... END_TYPE块用于定义新数据类型,不占用内存;STRUCT内部每个成员必须声明名称和具体数据类型(如INT、REAL、BOOL),不可省略;- 成员名区分大小写,
Speed和speed是两个不同成员;- 不支持嵌套未命名结构体(如
Config: STRUCT ... END_STRUCT必须先定义类型再使用)。
二、再声明变量:用类型创建实际实例
有了 MotorDataType 类型,还需声明一个变量来承载数据:
VAR_GLOBAL
MotorData : MotorDataType; // 全局变量,所有 POU 都可访问
END_VAR
或在某个程序块(如 MAIN)内部声明:
PROGRAM MAIN
VAR
MotorData : MotorDataType; // 局部变量,仅 MAIN 内有效
BackupData : MotorDataType; // 同类型另一实例
END_VAR
✅ 关键点:
MotorData是变量名,MotorDataType是它的类型;- 变量名不能与关键字(如
IF、FOR)、函数名或已声明类型名冲突;- 若声明为
VAR_GLOBAL,需确保项目配置允许全局变量(部分 PLC 平台要求启用“Global Variables”选项);- 同一作用域内不可重复声明同名变量(如
VAR MotorData : INT;和VAR MotorData : MotorDataType;冲突)。
三、正确访问成员:点号(.)是唯一合法操作符
ST 中访问结构体成员只允许使用英文半角点号 .,语法为:
变量名 . 成员名
因此,以下写法全部错误:
MotorData->Speed(C 语言风格,ST 不支持)MotorData[Speed](数组索引语法,不适用)MotorData("Speed")(函数调用语法,无效)MotorData.Speed()(误加括号,除非 Speed 是函数)
✅ 正确写法只有:
MotorData.Speed := 1500;
该语句含义明确:将整数值 1500 赋给 MotorData 变量中名为 Speed 的 INT 类型成员。
✅ 关键点:
.左侧必须是已声明的结构体变量(非类型名、非指针、非数组名);.右侧必须是该结构体类型中真实存在的成员名;- 编译器在编译期检查成员是否存在、类型是否匹配;若
Speed拼错为Speeed,立即报错:“member ‘Speeed’ not found in struct ‘MotorDataType’”。
四、支持多级嵌套:点号可连续使用
结构体成员本身也可以是结构体。例如扩展电机数据,加入编码器信息:
TYPE EncoderDataType : STRUCT
Position : DINT; // 32位位置值
Resolution : UINT; // 线数,如 2500
END_STRUCT
TYPE MotorDataType : STRUCT
Speed : INT;
Torque : REAL;
Encoder : EncoderDataType; // 成员是另一结构体
IsEnabled : BOOL;
END_STRUCT
此时访问编码器位置:
MotorData.Encoder.Position := 125000;
层级深度不限,只要每一级都正确定义、正确拼写。但建议嵌套不超过 3 层,避免可读性下降。
✅ 关键点:
MotorData.Encoder是EncoderDataType类型的子结构体变量;MotorData.Encoder.Position是其DINT类型成员;- 编译器逐级解析:先确认
MotorData有Encoder成员 → 再确认Encoder有Position成员 → 最后检查赋值类型兼容性(DINT←125000✅)。
五、赋值时的类型检查与隐式转换规则
ST 是强类型语言,赋值时严格校验类型兼容性。以 MotorData.Speed := 1500; 为例:
Speed是INT类型(通常为 16 位有符号整数,范围 −32 768 到 32 767);- 字面量
1500被编译器推断为INT,完全匹配,直接赋值;
但以下写法会触发警告或错误:
| 语句 | 结果 | 说明 |
|---|---|---|
MotorData.Speed := 50000; |
❌ 编译错误 | 超出 INT 范围,常量溢出 |
MotorData.Speed := 1500.0; |
⚠️ 警告(或错误,取决于设置) | REAL → INT 需显式转换,如 TRUNC(1500.0) |
MotorData.Speed := TRUE; |
❌ 编译错误 | BOOL 与 INT 无隐式转换 |
MotorData.Status := 16#03; |
✅ 正确 | BYTE 支持十六进制字面量,16#03 = 十进制 3 |
✅ 安全做法:
- 使用类型转换函数显式转换(如
INT(1500.0)、WORD(MotorData.Status));- 对临界值做范围校验(尤其来自 HMI 或通信的数据):
IF (NewSpeed >= 0) AND (NewSpeed <= 3000) THEN MotorData.Speed := NewSpeed; END_IF
六、初始化结构体:避免未定义值
PLC 上电后,未初始化的结构体成员值是随机的(RAM 初始状态)。必须显式初始化关键成员,防止逻辑误动作。
方法 1:声明时初始化(推荐)
VAR
MotorData : MotorDataType := (
Speed := 0,
Torque := 0.0,
Status := 16#00,
IsEnabled := FALSE,
FaultCode := 0
);
END_VAR
括号内顺序必须与 STRUCT 定义顺序一致,或使用成员名指定(IEC 61131-3 Ed. 3+ 支持):
MotorData : MotorDataType := (
.Speed := 0,
.IsEnabled := FALSE,
.Torque := 0.0
);
方法 2:首次扫描置零(兼容旧标准)
IF FIRST_SCAN THEN
MotorData.Speed := 0;
MotorData.IsEnabled := FALSE;
// ... 其他成员
END_IF
✅ 关键点:
FIRST_SCAN是常见系统变量(部分平台为GVL.FirstScan),代表 PLC 启动后第一个扫描周期;- 初始化必须覆盖所有安全相关成员(如
IsEnabled、Status),避免上电即启动。
七、调试技巧:如何验证结构体访问生效
仅写对语法不够,还需确认运行时值真实更新:
- 在线监控(Online Watch):在编程软件(TIA Portal、Codesys、Unity Pro)中添加
MotorData.Speed到监视表,观察值是否变为1500; - 强制写入测试:右键点击监视项 → “Force Value” → 输入
1500,观察设备响应; - 交叉引用检查:右键
MotorData.Speed→ “Find All References”,确认无其他位置意外改写该值; - 日志输出(如支持):
IF MotorData.Speed <> LastLoggedSpeed THEN LogMessage('Motor speed changed to ' + INT_TO_STRING(MotorData.Speed)); LastLoggedSpeed := MotorData.Speed; END_IF
八、常见错误与修复对照表
| 错误现象 | 可能原因 | 修复方式 |
|---|---|---|
| 编译报错 “MotorData not declared” | MotorData 未在当前作用域声明 |
检查 VAR 区域,确认变量已声明;若在子程序中使用,需传参或改为全局 |
| 编译报错 “‘Speed’ not a member of …” | STRUCT 定义中无 Speed 成员,或拼写错误 |
打开 TYPE 定义,逐字核对成员名;开启编辑器自动补全(输入 MotorData. 后弹出成员列表) |
| 运行时值不变 | 赋值语句未被执行(被 IF 条件屏蔽、位于未调用的 POU 中) | 添加临时调试变量 DebugFlag := TRUE;,在线查看是否置位;用断点单步执行 |
| 值显示为负数或极大值 | Speed 被声明为 USINT 但赋值超限,或内存重叠(如数组越界写入) |
检查变量声明类型;用内存视图查看 MotorData 起始地址周边数据是否异常 |
九、工程最佳实践:让结构体真正“自动化”
-
统一命名规范:
- 结构体类型名用
PascalCase(MotorDataType); - 实例变量名用
camelCase(motor1Data,conveyorAStatus); - 成员名用语义化小写(
speed,isRunning,faultCount),禁用缩写(如spd,en)。
- 结构体类型名用
-
按功能分组结构体:
TYPE MotorConfig : STRUCT // 参数类(只读或初始化时设) MaxSpeed : INT; AccTimeMs : TIME; END_STRUCT TYPE MotorRuntime : STRUCT // 运行时类(PLC 扫描中更新) Speed : INT; ActualTorque: REAL; RunTimeHrs : LREAL; END_STRUCT TYPE MotorIO : STRUCT // I/O 映射类(直接绑定物理地址) StartCmd : BOOL; // %QX100.0 StopCmd : BOOL; // %QX100.1 ReadySignal : BOOL; // %IX101.0 END_STRUCT -
避免“上帝结构体”:单个结构体成员数建议 ≤ 20。超过时拆分为关联结构体,例如:
MotorData.Electrical.Current,MotorData.Mechanical.Vibration,MotorData.Thermal.Temperature -
版本控制友好:在结构体注释中标明修订号和日期:
{ SCL: MotorDataType v2.1 — 2024-06-15 — added ‘ThermalAlarm’ } TYPE MotorDataType : STRUCT ... END_STRUCT
十、进阶:结构体数组与循环访问
当控制多台电机时,用结构体数组替代多个独立变量:
VAR
MotorArray : ARRAY[1..4] OF MotorDataType; // 4台电机
END_VAR
访问第 2 台电机转速:
MotorArray[2].Speed := 1200;
遍历所有电机设为停机:
FOR i := 1 TO 4 DO
MotorArray[i].IsEnabled := FALSE;
MotorArray[i].Speed := 0;
END_FOR
✅ 注意:数组下标从
1开始是习惯,也可用ARRAY[0..3](C 风格),但需全程统一。
十一、与其他语言对比:为什么 ST 这样设计?
| 特性 | ST(IEC 61131-3) | C 语言 | Python |
|---|---|---|---|
| 结构体定义 | TYPE T : STRUCT ... END_STRUCT |
struct { ... } t; |
class T: |
| 实例声明 | var : T; |
struct T var; |
var = T() |
| 成员访问 | var.member |
var.member 或 ptr->member |
var.member |
| 初始化 | := (...) 或 := ( .a := 1 ) |
{1, 2} 或 .a = 1 |
__init__() |
| 类型安全 | 编译期强检查 | 编译期弱检查(指针易出错) | 运行时动态(无编译期保障) |
ST 的设计目标是确定性、可验证、免指针错误。. 操作符的唯一性和编译期检查,正是为了杜绝内存越界、野指针等工业现场致命问题。
十二、最后验证:一行代码的完整生命周期
回到原始语句:MotorData.Speed := 1500;
- 编辑期:你输入该行,编辑器实时语法检查,确认
MotorData和Speed存在; - 编译期:编译器生成机器码,计算
MotorData起始地址 +Speed偏移量(如 0 字节),将1500编码为 16 位二进制写入该地址; - 下载期:代码烧录至 PLC RAM,
MotorData分配连续内存块(本例至少 10 字节); - 运行期:每个扫描周期执行该指令,CPU 直接向内存地址写入
0x05DC(1500 十六进制); - 监控期:上位软件通过通信协议读取该地址内容,显示为
1500。
它不是魔法,而是一条被精确控制、全程可追溯的数据通路。
MotorData.Speed := 1500;

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