Chapter 12 S7 驱动专题
S7 是最早支持、也用得最多的驱动。它把西门子 PLC 的存储区(DB 块、M 区)映射成测点:地址写 DB200.100 这样的字符串,剩下的交给库。
用法三步,和别的驱动没有区别:
- 配通道:XML 里写一个
driver="S7"的<Channel>,填 IP 与机架/槽位(见 12.1); - 配测点:
<Tag address="DB200.100.1" type="BIT">,地址是区域加偏移(见 12.2); - 注册
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>
| 子元素 | 默认 | 说明 |
|---|---|---|
IpAddr | localhost | PLC 的地址 |
Rack | 0 | 机架号(short) |
Slot | 1 | 槽位号(short)。S7-1200/1500 常见为 1,S7-300/400 视硬件组态而定 |
ConnectionType | 3 | 连接类型: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 格式,必须给 maxlen | OpcUa 无此概念;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 的说明) |