Chapter 12 S7 驱动专题

S7 是最早支持、也用得最多的驱动。它把西门子 PLC 的存储区(DB 块、M 区)映射成测点:地址写 DB200.100 这样的字符串,剩下的交给库。

用法三步,和别的驱动没有区别:

  1. 配通道:XML 里写一个 driver="S7" 的 <Channel>,填 IP 与机架/槽位(见 12.1);
  2. 配测点:<Tag address="DB200.100.1" type="BIT">,地址是区域加偏移(见 12.2);
  3. 注册 builder.AddS7Support()(见 12.4)。

配套包:StdUnit.Tags.S7。

本系列文档只讲"怎么用、为什么这样设计、容易踩什么"。地址全表、类型全表、字段默认值请查 AI 参考手册 的 §5.1;两者冲突时以手册为准。

12.1 通道配置

<Project xmlns:s7="tags:s7"
         xsi:noNamespaceSchemaLocation="Schemas/tagsproject.xsd"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Channel name="S7-1" driver="S7">
    <s7:IpAddr>192.168.10.2</s7:IpAddr>
    <s7:Rack>0</s7:Rack>
    <s7:Slot>1</s7:Slot>
    <s7:ConnectionType>3</s7:ConnectionType>
  </Channel>
</Project>
子元素默认说明
IpAddrlocalhostPLC 的地址
Rack0机架号(short)
Slot1槽位号(short)。S7-1200/1500 常见为 1,S7-300/400 视硬件组态而定
ConnectionType3连接类型:0 是 PG、1 是 OP、3 是 PG 内部(默认值沿用旧行为)

这四个字段的解析失败都会在加载期抛 TagsProjectXmlException,消息里带 Channel(通道名),不会等到连接时才发现。

12.2 地址怎么写

S7 的地址语法是"区域 + 块号(可选)+ 偏移 + 位号(可选)":

写法区域例子
DB<块号>.<偏移>数据块DB200.100
DB<块号>.<偏移>.<位号>数据块的某一位(位号 0~15)DB200.100.1
MB.<偏移>M 区(Merker)MB.20
MB.<偏移>.<位号>M 区的某一位MB.20.3
$$<偏移>相对地址:沿用所属组合的区域与块号$$104、$$104.3

几个容易误解的地方:

  • $$ 后面的数字是块内的绝对偏移,不是相对偏移。例如组合起始地址是 DB200.100,子测点写 $$100,它指向的仍是 DB200.100——$$ 只省掉"重复写区域与块号",不改变偏移的算法。
  • 组合的起始地址必须是绝对地址。$$ 的含义是"沿用上层",而组合自身没有可沿用的上层。
  • 子测点若写了绝对地址,必须与组合同区域同块号,否则加载期直接报错,并提示改用 $$。这是为了避免"看起来在同一个组合里、实际读到另一个块"的沉默错误。
  • 位号支持 0~15:大于 7 的部分会被换算到下一个字节(8 表示下一个字节的第 0 位)。不要写超过 15 的值——库只把"除以 8 的余数"当作位号,更大的值(如 16)会静默落到和 8 相同的位置,读到的不是你写的那个位。

12.3 类型与端序

支持的 type:BIT、BYTE、INT16、UINT16、INT32、UINT32、INT64、UINT64、FLOAT、STR。占用的字节数由类型决定(BIT 与 BYTE 是 1 字节,INT16/UINT16 是 2 字节,INT32/UINT32/FLOAT 是 4 字节,INT64/UINT64 是 8 字节),不写 TagSize 时按类型自动补。

多字节类型必须写 endian="BigEndian"

这是本章最需要注意的一点。库的全局默认端序是 LittleEndian,而西门子 PLC 是大端,所以多字节类型(INT16、UINT16、INT32、UINT32、INT64、UINT64、FLOAT)请一律显式指定:

<Tag name="程序号" address="DB400.2" type="INT16" endian="BigEndian"></Tag>
<Tag name="最终扭矩" address="DB401.18" type="INT32" endian="BigEndian"></Tag>

漏写不会报错,只会得到字节颠倒的数值——这类错误在现场看起来像"PLC 数据不对",在现场和电气工程师对点时很容易发现这种字节序的问题。BIT、BYTE、STR 不涉及多字节组装,写不写都一样。

端序的取值大小写敏感:BigEndian、LittleEndian,其它写法(如 bigendian)会在加载期报错。

字符串(STR)

西门子 PLC 中的字符串在其内存里是"2 个字节的头 + 内容":头两个字节分别记录总容量与当前有效长度。所以测点必须额外写 maxlen(1~255),库据此把 TagSize 算成 2 + maxlen,并在组合初始化时把容量写进缓存头部。

<Tag name="拍照-请求-PN" address="$$108" type="STR" maxlen="16" />

不写 maxlen、或写成非正整数,都会在加载期抛 TagsProjectXmlException。

12.4 注册与运行

builder.AddS7Support();                   // 等价于手动注册Channel + TagCbntBuilder + DirectTagBuilder + XSD provider
builder.EnableXmlSchemaValidation();      // 可选:让 <s7:IpAddr> 等子元素有智能提示与加载期校验

想要更精细的控制,可以手动调用 AddS7Channel()、AddS7TagCbntBuilder()、AddS7DirectTagBuilder() 自由组合。

运行方式与其它驱动完全一致:

await ctrl.StartPollAsync(dir, root: null, hook: (proj, sp, ct) =>
{
    // 需要时在这里补逻辑组件
    return Task.CompletedTask;
});

什么时候该用 <TagCbnt>(组合)

S7 的读写是按"一段连续的字节"发出去的,所以组合在这里的收益很实在:把同一块区域里相邻的测点放进一个 <TagCbnt>,一轮就只发一次读/写请求,而不是每个测点各发一次。

<TagCbnt name="拍照请求" access="RO" address="DB200.100">
  <Tag name="拍照-请求-标志"   address="$$100.1" type="BIT" />                      <!-- 字节 100 的第 1 位 -->
  <Tag name="拍照-请求-料号"   address="$$102"   type="INT16" endian="BigEndian" />  <!-- 字节 102~103 -->
  <Tag name="拍照-请求-程序号" address="$$104"   type="INT32" endian="BigEndian" />  <!-- 字节 104~107 -->
  <Tag name="拍照-请求-PN"     address="$$108"   type="STR" maxlen="16" />           <!-- 字节 108~125 -->
</TagCbnt>

组合内部的字节缓存由库按各测点的偏移和长度自动排版(AutoLayout),你只需保证地址落在组合覆盖的范围里、且互相不重叠。上面这个组合覆盖了 DB200.100 到 DB200.125(共 26 字节,其中 STR 占 2 + 16),所以读一次就能拿到全部四个测点。

注意地址要与 PLC 侧的 DB 布局对齐:库不会替你做对齐填充,它完全按你写的偏移紧密排版。而西门子 PLC 的 DB 里,多字节变量通常要求偶数地址起始(INT16/INT32/REAL/STRING 都是如此),所以在 PLC 里定义变量时最好就按偶数地址排布,XML 里照抄即可。上面例子里 INT16 从 102 起、INT32 从 104 起、STRING 从 108 起,都是偶数;如果 PLC 侧实际偏移是奇数(例如 INT16 在 103),XML 里也必须写 103,否则读到的将是隔壁变量的字节。

S7 也支持直连测点(不在组合里的 <Tag>),适合"就一个孤立的点、不值得为它建组合"的场景;代价是每次都要单独发一次请求。

为了性能考虑,我们建议你尽量使用 <TagCbnt> 来组合测点,而不是把 <Tag> 直接套进 <TagGrp>。

12.5 它和其它驱动不一样的地方

S7其它驱动(对比)
地址区域 + 块号 + 偏移 + 位号,支持位寻址与 $$ 相对地址Modbus 是功能码区域加参考号,OpcUa 是 NodeId
端序默认 LittleEndian 与 PLC 相反,多字节类型必须显式声明 BigEndian语义不同:endian 同为"每个 16 位单元内部两字节的顺序",但 32/64 位还多一个 interpret(单元之间的顺序),见 §5.2(详见 Chapter 14)
字符串有"两字节头"的 PLC 格式,必须给 maxlenOpcUa 无此概念;SimpleFiles 是纯文本
底层同步实现,靠线程包装成异步OpcUa 原生异步

最后一条带来的后果:S7 通道的读写实际占用线程,且同一通道上的读、写、断开是串行的(内部有互斥)。这一点在"一个入口里配了很多 S7 通道"时值得留意——入口之间是并行的,但每个通道内部不会并发。

12.6 排错清单

现象先看这里
数值像是"字节颠倒"多字节类型漏了 endian="BigEndian"(见 12.3)。这是最常见的 S7 配置错误
加载期报"组合自身必须写绝对地址"<TagCbnt> 的 address 写了 $$...。组合的起始地址必须写 DB<块>.<偏移> 或 MB.<偏移>
加载期报"不在同一区域/DB 块"组合内的子测点写了绝对地址,但区域或块号与组合不一致。要么改成同块,要么改用 $$ 相对地址
加载期报 maxlen 相关错误STR 遗漏了 maxlen,或者值不是 1~255 的正整数
连接失败看消息:通道(S7-1) 连接PLC失败:{IP} Rack=.. Slot=..,后面跟着底层错误码。先确认 Rack/Slot 与硬件组态一致、PLC 允许 PUT/GET
读写失败消息形如 通道(S7-1) 读取DB失败:DB200.100,长度=3;...,包含区域、块号、偏移、长度与底层错误码明细,可据此定位是地址越界还是链路问题
报"不支持"的地址区域检查地址字符串本身;$$ 只能出现在组合内部的子测点上
位测点的值总是同一个位位号写超过 15 了(见 12.2)。库只取"除以 8 的余数",16 与 8 会落到同一个位置
读到的数值像是隔壁变量地址与 PLC 的 DB 布局不一致:库不做对齐填充,请按 PLC 侧的实际偏移写(见 12.4 的说明)