文章目录

西门子PLC的字符串格式化输出

发布于 2026-03-23 15:32:29 · 浏览 3 次 · 评论 0 条

西门子PLC的字符串格式化输出是将数值、时间、状态等信息转换为可读文本的核心技术,广泛应用于HMI显示、日志记录、通信报文构建等场景。本文基于S7-1200/1500系列,从基础指令到工程实战,完整拆解实现方法。


一、核心指令:S7-1200/1500的字符串处理基础

1.1 字符串数据类型结构

西门子PLC的字符串由最大长度当前长度字符数组三部分组成。以 String[20] 为例:

字节偏移 内容 说明
0 20 最大允许字符数(声明时固定)
1 实际长度 当前存储的有效字符数
2~21 字符数据 ASCII码存储,每个字符占1字节

关键认知:修改字符串时,PLC自动维护字节1的当前长度值,无需手动计算。

1.2 必备指令清单

指令名称 功能 典型应用场景
Strg_VAL 字符串转数值 解析扫码枪、仪表返回的ASCII数据
Val_STRG 数值转字符串 将温度、压力等过程值格式化为显示文本
Strg_Len 获取字符串长度 计算报文总长度、校验累加
Concat 字符串拼接 组合多字段报文(如"温度:25.5℃")
Left/Right/Mid 截取子串 提取设备地址、指令代码
Replace 替换子串 模板化生成相似格式的多组报文
Find 搜索子串位置 解析变长协议中的关键字定位

二、Val_STRG 深度解析:数值到字符串的格式化

Val_STRG 是实现格式化输出的核心指令,支持整数、浮点数的多种格式控制。

2.1 引脚功能详解

IN:待转换的数值(支持INT/DINT/REAL等)
SIZE:输出字符串的总宽度(含符号、小数点、前导零/空格)
PREC:小数位数(仅对REAL有效,整数自动忽略)
FORMAT:格式控制字(16位,决定对齐、进制、符号等)
OUT:目标字符串变量

2.2 FORMAT控制字位定义

名称 功能
0~3 对齐方式 0=右对齐左补空, 1=左对齐右补空, 2=右对齐左补零
4~7 符号处理 0=负数加负号正数不加, 1=始终显示符号
8~11 进制选择 0=十进制, 1=十六进制, 2=八进制
12~15 保留 必须为0

常用组合计算

  • 右对齐左补零 + 正负都显符号 + 十进制 = 2 + (1<<4) + 0 = 18(十六进制16#0012
  • 左对齐 + 默认符号 + 十进制 = 1(十六进制16#0001

2.3 典型调用示例

场景A:温度值格式化(右对齐左补零,保留1位小数)

// 假设:当前温度25.5℃,需格式化为"0025.5"(7字符宽)
"Temp_Real" := 25.5;
"FormatCode" := 16#0002;  // 右对齐左补零

Val_STRG(
    IN := "Temp_Real",
    SIZE := 7,
    PREC := 1,
    FORMAT := "FormatCode",
    OUT => "TempString"
);
// 结果:TempString = "0025.5 "(注意末尾可能有1个空格补足)

场景B:设备编号左对齐(无符号整数)

// 设备号1234,格式化为"1234   "(8字符左对齐)
"DeviceID" := 1234;
"FormatCode" := 16#0001;  // 左对齐

Val_STRG(
    IN := "DeviceID",
    SIZE := 8,
    PREC := 0,
    FORMAT := "FormatCode",
    OUT => "IDString"
);
// 结果:IDString = "1234    "

场景C:十六进制地址显示

// 内存地址2047,显示为"07FF"(4字符大写十六进制)
"MemAddr" := 16#07FF;
"FormatCode" := 16#0200;  // 十六进制模式(位8~11=2)

Val_STRG(
    IN := "MemAddr",
    SIZE := 4,
    PREC := 0,
    FORMAT := "FormatCode",
    OUT => "HexString"
);
// 结果:HexString = "07FF"

三、复杂格式化:多字段组合与动态模板

实际工程中,单条 Val_STRG 往往不够,需要指令组合实现复杂格式。

3.1 固定格式报文生成

目标格式T:25.5|P:1.02|F:1560(温度、压力、流量三字段)

// 步骤1:分别转换三个数值
Val_STRG(IN:="Temp", SIZE:=4, PREC:=1, FORMAT:=16#0002, OUT=>"StrTemp");
Val_STRG(IN:="Press", SIZE:=4, PREC:=2, FORMAT:=16#0002, OUT=>"StrPress");
Val_STRG(IN:="Flow", SIZE:=4, PREC:=0, FORMAT:=16#0002, OUT=>"StrFlow");

// 步骤2:拼接前缀和数值
Concat(IN1:="T:", IN2:="StrTemp", OUT=>"Part1");
Concat(IN1:="|P:", IN2:="StrPress", OUT=>"Part2");
Concat(IN1:="|F:", IN2:="StrFlow", OUT=>"Part3");

// 步骤3:最终组合
Concat(IN1:="Part1", IN2:="Part2", OUT=>"TempCombine");
Concat(IN1:="TempCombine", IN2:="Part3", OUT=>"FinalMsg");

3.2 动态宽度与条件格式

当数值范围变化大时,需动态计算SIZE避免截断或过度补零。

// 自动适应整数位数(以DINT为例)
IF "TargetValue" >= 100000 THEN
    "CalcSize" := 7;
ELSIF "TargetValue" >= 10000 THEN
    "CalcSize" := 6;
ELSIF "TargetValue" >= 1000 THEN
    "CalcSize" := 5;
ELSIF "TargetValue" >= 100 THEN
    "CalcSize" := 4;
ELSIF "TargetValue" >= 10 THEN
    "CalcSize" := 3;
ELSE
    "CalcSize" := 2;  // 至少保留1位数字+可能的符号
END_IF;

Val_STRG(
    IN := "TargetValue",
    SIZE := "CalcSize",
    PREC := 0,
    FORMAT := 16#0000,  // 右对齐左补空格
    OUT => "AdaptiveStr"
);

3.3 时间戳格式化(DATE_AND_TIME转字符串)

标准时间类型需先拆分为组件,再分别格式化。

// 读取系统时间并拆解
"LocalTime" := READ_RTC();  // 或 NTP 同步后的时间

// 提取各时间分量(使用IEC功能块)
YEAR := "LocalTime".YEAR;
MONTH := "LocalTime".MONTH;
DAY := "LocalTime".DAY;
HOUR := "LocalTime".HOUR;
MINUTE := "LocalTime".MINUTE;
SECOND := "LocalTime".SECOND;

// 格式化为 "2024-01-15 08:30:45"
Val_STRG(IN:=YEAR, SIZE:=4, PREC:=0, FORMAT:=16#0002, OUT=>"StrYear");
Val_STRG(IN:=MONTH, SIZE:=2, PREC:=0, FORMAT:=16#0002, OUT=>"StrMon");
Val_STRG(IN:=DAY, SIZE:=2, PREC:=0, FORMAT:=16#0002, OUT=>"StrDay");
Val_STRG(IN:=HOUR, SIZE:=2, PREC:=0, FORMAT:=16#0002, OUT=>"StrHour");
Val_STRG(IN:=MINUTE, SIZE:=2, PREC:=0, FORMAT:=16#0002, OUT=>"StrMin");
Val_STRG(IN:=SECOND, SIZE:=2, PREC:=0, FORMAT:=16#0002, OUT=>"StrSec");

// 逐步拼接(为清晰分多步,实际可优化)
Concat(IN1:="StrYear", IN2:="-", OUT=>"TS1");
Concat(IN1:="TS1", IN2:="StrMon", OUT=>"TS2");
Concat(IN1:="TS2", IN2:="-", OUT=>"TS3");
Concat(IN1:="TS3", IN2:="StrDay", OUT=>"TS4");
Concat(IN1:="TS4", IN2:=" ", OUT=>"TS5");
Concat(IN1:="TS5", IN2:="StrHour", OUT=>"TS6");
Concat(IN1:="TS6", IN2:=":", OUT=>"TS7");
Concat(IN1:="TS7", IN2:="StrMin", OUT=>"TS8");
Concat(IN1:="TS8", IN2:=":", OUT=>"TS9");
Concat(IN1:="TS9", IN2:="StrSec", OUT=>"TimeStampStr");

四、高级技巧:数组循环与批量处理

4.1 批量设备状态上报

假设10台设备,每台需生成 "Dev01:RUN,T:45.2" 格式报告。

FOR #i := 0 TO 9 DO
    // 计算设备编号字符串(01~10)
    "TempDevNum" := #i + 1;
    Val_STRG(IN:="TempDevNum", SIZE:=2, PREC:=0, FORMAT:=16#0002, OUT=>"DevNumStr");

    // 获取该设备状态(假设从DB数组读取)
    "DevStatus" := "DeviceDB".Status[#i];  // 0=STOP, 1=RUN, 2=ALARM
    "DevTemp" := "DeviceDB".Temperature[#i];

    // 状态码转字符串
    CASE "DevStatus" OF
        0: "StatusStr" := "STOP";
        1: "StatusStr" := "RUN ";
        2: "StatusStr" := "ALARM";
    ELSE
        "StatusStr" := "ERR ";
    END_CASE;

    // 格式化温度
    Val_STRG(IN:="DevTemp", SIZE:=4, PREC:=1, FORMAT:=16#0002, OUT=>"TempStr");

    // 组合单条记录:Dev01:RUN ,T:45.2
    Concat(IN1:="Dev", IN2:="DevNumStr", OUT=>"LineStart");
    Concat(IN1:="LineStart", IN2:=":", OUT=>"Line1");
    Concat(IN1:="Line1", IN2:="StatusStr", OUT=>"Line2");
    Concat(IN1:="Line2", IN2:=",T:", OUT=>"Line3");
    Concat(IN1:="Line3", IN2:="TempStr", OUT=>"DeviceReport"[#i]);

    // DeviceReport为String[20]数组,存储10条记录
END_FOR;

4.2 变长JSON风格报文构建

现代物联网通信常需JSON格式,需处理引号、逗号等特殊字符。

// 目标:{"temp":25.5,"alarm":false,"msg":"normal"}
// 注意:JSON中的双引号在PLC字符串中用两个双引号表示(转义)

// 数值部分
Val_STRG(IN:="SensorTemp", SIZE:=4, PREC:=1, FORMAT:=16#0000, OUT=>"JsonTemp");
Val_STRG(IN:="AlarmCount", SIZE:=3, PREC:=0, FORMAT:=16#0000, OUT=>"JsonAlarm");

// 构建组件(使用常量字符串)
// 常量定义:"开头大括号、字段名等
"{""temp"":""  存储在 "JsonPartA"(实际内容为 {"temp":)
"","alarm":"" 存储在 "JsonPartB"
",""msg"":""" 存储在 "JsonPartC"
"""}""        存储在 "JsonPartD"(实际内容为 "})

// 拼接流程
Concat(IN1:="JsonPartA", IN2:="JsonTemp", OUT=>"J1");
Concat(IN1:="J1", IN2:="JsonPartB", OUT=>"J2");
Concat(IN1:="J2", IN2:="JsonAlarm", OUT=>"J3");
Concat(IN1:="J3", IN2:="JsonPartC", OUT=>"J4");

// 处理动态消息文本(假设"MsgText"已赋值如"normal")
Concat(IN1:="J4", IN2:="MsgText", OUT=>"J5");
Concat(IN1:="J5", IN2:="JsonPartD", OUT=>"FinalJson");

关键提示:S7-1200/1500的字符串常量中,双引号用连续两个表示(""),这是IEC 61131-3标准的转义规则。


五、调试与常见问题

5.1 典型故障排查

现象 原因 解决
输出字符串为乱码 目标变量长度声明不足 检查 String[XX] 的XX是否大于SIZE参数
数值被截断显示##### SIZE小于实际数字位数 增加 SIZE或改用左对齐模式
浮点数精度丢失 REAL类型仅6~7位有效数字 对高精度需求改用LREAL+String[40]
中文显示异常 PLC字符串仅支持ASCII 中文需用Unicode编码或转交HMI处理
拼接后长度超限 中间结果超过String声明长度 拆分为多次短拼接,或使用更大容量String

5.2 在线调试技巧

  1. 强制监控:在博图软件的监控表中,将字符串变量格式设为"字符串"而非默认的"十六进制",直观查看结果。
  2. 长度验证:同时监控字符串变量的第1字节(当前长度),确认与预期一致。
  3. 分步测试:复杂组合逻辑建议用临时中间变量存储每步结果,逐步验证。

六、S7-1500增强功能(与S7-1200差异)

功能 S7-1200 S7-1500
字符串最大长度 254字节 16382字节(String类型)
宽字符支持 不支持原生WString处理 支持WString(Unicode,每字符2字节)
S7String直接操作 需转换 可直接处理SIMATIC字符串格式
性能 基础指令集 新增AT覆盖、Variant指针等高效操作

S7-1500专用:使用VARIANT实现泛型格式化

// 通过VARIANT指针,同一段代码处理不同类型
"AnyToString"(
    value := "InputVariant",  // 可指向INT/REAL/DINT等任意类型
    format := "%08.3f",       // 类C语言格式描述符
    result => "OutputString"
);

七、完整工程代码模板

以下为一个可直接导入的功能块 FB_StringFormatter,封装常用格式化模式。

FUNCTION_BLOCK "FB_StringFormatter"
VAR_INPUT
    Enable      : Bool;           // 使能信号
    Mode        : Byte;           // 0=标准数值, 1=时间戳, 2=设备状态
    ValueReal   : Real;           // 浮点输入
    ValueDInt   : DInt;           // 整数输入
    Precision   : Byte := 1;      // 小数位(Mode=0时有效)
    TotalWidth  : Byte := 8;      // 总宽度
END_VAR
VAR_OUTPUT
    Done        : Bool;
    ResultStr   : String[30];
END_VAR
VAR_TEMP
    TempStr1, TempStr2 : String[15];
    FormatCode  : Word;
    Year, Mon, Day, Hour, Min, Sec : Int;
END_VAR

CASE #Mode OF
    0:  // 标准数值模式
        IF ABS(#ValueReal) >= 100000.0 THEN
            // 大数用科学计数或调整宽度
            #FormatCode := 16#0002;  // 右对齐左补零
        ELSE
            #FormatCode := 16#0000;  // 右对齐左补空格
        END_IF;

        Val_STRG(
            IN := #ValueReal,
            SIZE := UINT_TO_INT(#TotalWidth),
            PREC := UINT_TO_INT(#Precision),
            FORMAT := #FormatCode,
            OUT => #ResultStr
        );
        #Done := TRUE;

    1:  // 时间戳模式(ValueDInt作为Unix时间或专用结构)
        // 简化示例:假设ValueDInt分解为YYMMDDhhmmss
        #Year := DINT_TO_INT(#ValueDInt / 10000000000);
        #Mon  := DINT_TO_INT((#ValueDInt / 100000000) MOD 100);
        #Day  := DINT_TO_INT((#ValueDInt / 1000000) MOD 100);
        // ... 类似提取时分秒,然后按3.3节拼接
        #ResultStr := "TimeMode_TODO";  // 实际需完整实现
        #Done := TRUE;

    2:  // 设备状态模式
        Val_STRG(IN:=#ValueDInt, SIZE:=3, PREC:=0, FORMAT:=16#0002, OUT=>#TempStr1);
        Concat(IN1:="DEV", IN2:=#TempStr1, OUT=>#TempStr2);
        // 根据ValueReal判断状态(0=STOP, 1=RUN)
        IF #ValueReal > 0.5 THEN
            Concat(IN1:=#TempStr2, IN2:=":RUN ", OUT=>#ResultStr);
        ELSE
            Concat(IN1:=#TempStr2, IN2:=":STOP", OUT=>#ResultStr);
        END_IF;
        #Done := TRUE;

ELSE
    #ResultStr := "MODE_ERR";
    #Done := FALSE;
END_CASE;

END_FUNCTION_BLOCK

调用示例

// 格式化当前转速1560.5 RPM,8字符宽,1位小数
"Formatter".Enable := TRUE;
"Formatter".Mode := 0;
"Formatter".ValueReal := "MotorSpeed";
"Formatter".Precision := 1;
"Formatter".TotalWidth := 8;
// 结果:"  1560.5"(左补两个空格)

掌握上述方法后,可应对西门子PLC中绝大多数字符串格式化需求。核心要点在于:理解String数据结构、熟练运用Val_STRG的FORMAT控制、合理拆分复杂格式为多步简单操作

评论 (0)

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

扫一扫,手机查看

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