终于有人把西门子PLC字符串说明白了
大家好!我是付工。
今天跟大家分享一下西门子PLC的字符串读写,希望这篇文章可以让大家彻底弄明白西门子PLC字符串的相关知识。
字符串说明
字符串是西门子PLC通信中最容易出错的数据类型。
西门子 S7 系列 PLC 的字符串类型包含 String 与 WString 两种,这两种在内存布局上与 C# 的 String 完全不同。
西门子字符串携带内联长度头、特定编码格式,后者则由 .NET 运行时管理长度。因此,读写西门子字符串若不加处理,直接按字节读取,头部长度信息会混入字符串内容,导致乱码或截断。
为了区分,后续阐述时,我们将西门子PLC的String类型与WString字符串规定为S7 String与S7 WString。
本文首先剖析 S7 String 与 S7 WString 的存储结构,再给出基于 S7netplus 的正确读写方案。
S7 String存储结构
S7 String 在 PLC 侧以 String[N] 形式声明,其中 N 为最大字节数,最大值为254。如果在PLC侧直接声明类型为String,等同于String[254]。S7 String内存布局为 2 字节头 + N 字节数据区,具体如表所示:

编码说明:S7 String 的字符编码并非 ASCII,在中文环境下使用博图(TIA Portal)给 String 赋值时,汉字会以 GBK 编码写入数据区,如果使用 ASCII 解码,必然乱码。因此涉及中文的场景应优先使用使用WString类型存储,如果必须使用S7 String类型,应该使用GBK编码格式进行解析。
下面通过实际示例对S7 String存储结构进行说明,在PLC侧DB2中创建两个S7 String变量A与B,数据类型分别为String[5]和String[10],给A赋值“ABC”,给B赋值“你好”,同时创建一个TestA和TestB变量,便于观察偏移量,如图所示:

我们可以直观看到变量A实际占用7个字节,变量B实际占用12个字节,符合S7String内存2+N的布局规则。为了进一步观察变量A和变量B的实际数据,我们创建一个监控表进行监视,如图所示:

从图中可以做出如下分析:
(1)变量A的字节头为0x05 0x03,表示最大长度为5,有效长度为3。数据区为0x41 0x42 0x43 0x00 0x00,前3个有效字节0x41 0x42 0x43即对应“ABC”。
(2)变量B字节头为0x0A 0x04,表示最大长度为10,有效长度为4,每个中文占用2个字节。数据区为0xC4 0xE3 0xBA 0xC3 0x00 0x00 0x00 0x00 0x00 0x00,将前4个有效字节按照GBK编码格式转换成中文即为“你好”。
S7 String读写
1、读取S7 String字符串
S7netplus 内置了对 S7 String字符串类型读取的封装,通过 VarType.S7String指定变量类型,库会自动处理头部解析与编码转换。S7netplus内置的编码格式为ASCII,因此对于非中文字符串可以直接读取,读取变量A的值代码如下:
//读取S7 String(变量A为String[5])
string strA = (string)siemens.Read(DataType.DataBlock,2,0,VarType.S7String,5);
//运行结果
变量A的值为ABC
对于中文字符串,需要手动设置一下编码格式,读取变量B的值代码如下:
//手动设置编码格式
S7String.StringEncoding = Encoding.GetEncoding("GBK");
//读取S7 String(变量B为 String[10])
string strB = (string)siemens.Read(DataType.DataBlock,2,8,VarType.S7String,10);
//运行结果
变量B的值为你好
对于.Net Framework框架上述代码可以直接运行,针对.NET 5及以上框架,需要注册编码提供程序,否则运行时会报错。在程序启动时调用一次,通过放在Program.cs中,注册代码如下:
using System.Text;
//注册代码页编码提供程序,使GBK等编码可用
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
2、写入S7 String字符串
S7netplus并不支持直接写入S7 String字符串,因此我们需要通过手动构建字节数组,然后调用WriteBytes来实现,代码如下:
public void WriteString(int db, int startByte, int maxLen, string value)
{
Encoding gbk = Encoding.GetEncoding("GBK");
byte[] encoded = gbk.GetBytes(value);
// 手动构建 S7 String 格式:[MaxLen][ActLen][数据...][填充0x00]
byte[] buffer = new byte[2 + maxLen];
buffer[0] = (byte)maxLen; // MaxLen — PLC 声明的最大长度
buffer[1] = (byte)encoded.Length; // ActLen — 实际字节数(非字符数)
Buffer.BlockCopy(encoded, 0, buffer, 2, encoded.Length);
// 剩余部分自动为 0x00,无需手动填充
siemens.WriteBytes(DataType.DataBlock, db, startByte, buffer);
}
我们可以直接调用该方法将变量A修改为“CBA”,变量B修改为“信必达”,代码如下:
private void btn_Write_Click(object sender, EventArgs e)
{
WriteString(2, 0, 5, "CBA");
WriteString(2, 8, 10, "信必达");
}

S7 WString存储结构
S7 WString 在PLC侧以WString[N]形式声明,编码格式固定为UTF16,其中N为最大字符数,最大值为16382。如果在PLC侧直接声明类型为WString,等同于WString[254]。S7 WString内存布局采用 4 字节头 + 2N 字节 UTF-16 BE 数据区,具体如表所示:

编码说明:WString 的编码格式固定为 UTF-16(大端序),与 PLC 系统字符集无关,天然支持中文等 Unicode 字符。其中 ActLen 记录的是字符数,而非字节数,这与 S7 String 的 ActLen(字节数)有本质区别。
下面通过示例对S7 WString存储结构进行说明,在PLC侧DB3中创建两个S7 WString变量A与B,数据类型分别为WString[5]和WString[10],给A赋值“ABC”,给B赋值“你好”,同时创建一个TestA和TestB变量,便于观察偏移量,如图所示:

我们可以直观看到变量A实际占用14个字节,变量B实际占用24个字节,符合WString内存4+2N的布局规则。为了进一步观察变量A和变量B的实际数据,我们创建一个监控表进行监视,由于数据较多,我们以变量A来观察,如图所示:

从图中可以看到,S7 WString并不是以字节为单位,而是以UTF-16字符为单位,同样的“ABC”,在S7 WString中,字节头变成0x00 0x05 0x00 0x03,数据区变成0x00 0x41 0x00 0x42 0x00 0x43 0x00 0x00 0x00 0x00,这也是S7 WString天然支持中文等 Unicode 字符的原因。
S7 WString读写
1、读取S7 WString字符串
S7netplus内置了对S7 WString字符串类型读取的封装,通过 VarType.S7WString指定变量类型,库会自动处理头部解析与编码转换,与S7 String类似,代码如下:
//读取 S7 WString(变量A为 WString[5])
string strA = (string)siemens.Read(DataType.DataBlock, 3, 0, VarType.S7WString, 5);
//手动设置编码格式
S7String.StringEncoding = Encoding.GetEncoding("GBK");
//读取S7 String(变量B为 WString[10])
string strB = (string)siemens.Read(DataType.DataBlock, 3, 16, VarType.S7WString, 10);
2、写入S7 WString字符串
S7netplus同样不支持写入S7 WString字符串,通过手动构建 4 字节头 + UTF-16 BE 数据,然后调用WriteBytes来实现,编写写入方法代码如下:
public void WriteWString(int db, int startByte, int maxLen, string value)
{
// UTF-16 BE 编码(与 PLC WString 一致)
byte[] encoded = Encoding.BigEndianUnicode.GetBytes(value);
// 手动构建 S7 WString 格式:[MaxLen 2B BE][ActLen 2B BE][UTF-16数据...][填充]
byte[] buffer = new byte[4 + maxLen * 2];
// MaxLen — 大端序
buffer[0] = (byte)(maxLen >> 8);
buffer[1] = (byte)(maxLen & 0xFF);
// ActLen — 大端序,此处为字符数(非字节数)
buffer[2] = (byte)(value.Length >> 8);
buffer[3] = (byte)(value.Length & 0xFF);
// 数据区
Buffer.BlockCopy(encoded, 0, buffer, 4, encoded.Length);
siemens.WriteBytes(DataType.DataBlock, db, startByte, buffer);
}
调用WriteWString方法将变量A修改为“CBA”,变量B修改为“信必达”,修改结果如图所示:
