# Siemens S7

> 按地址通过 S7comm，或按变量名通过 S7CommPlus 连接 Siemens S7 PLC：“访问”字段、机架和槽位、存储区、定型的字节/位寻址、变量名、HMI 设置，以及那些原生数据类型。

**Siemens S7** 驱动通过 S7comm 和 Siemens S7 PLC 对话，也就是承载在 ISO-on-TCP 上的那个经典 Step 7 协议。它用的是绝对寻址，即存储区、数据块号、字节和位，所以不需要从工程项目里导出任何符号表就能工作。在 [Connector](connector) 里的 Siemens S7 驱动下面添加一台设备，把下面这些字段填上；面板只显示这个协议用得到的那些字段。

以上是**按地址**访问。每台设备选择自己的**访问**方式：上面说的**按地址**，或者**按名称**，走 S7CommPlus，也就是 TIA Portal 和 Siemens HMI 使用的协议；按名称时，每个点位按 TIA Portal 的写法写出它的变量，类型由 CPU 告知。

## 访问 {#access}

| 方面 | 按地址 | 按名称 |
| --- | --- | --- |
| 协议 | ISO-on-TCP 上的 S7comm，端口 102 | ISO-on-TCP 中 TLS 1.3 上的 S7CommPlus，端口 102 |
| CPU | S7-300、S7-400、S7-1200、S7-1500 | 固件 V2.9 或更新的 S7-1500、固件 V4.5 或更新的 S7-1200，并且在 TIA Portal V17 或更新版本中组态 |
| 设备的字段 | 主机、机架 / 槽位 | 主机、用户名和密码，两者都可不填 |
| 点位写的是什么 | 存储区、DB 号、字节和位，以及它的数据类型 | 变量的名称，例如 `GanterOpt.Real_`；类型由 CPU 告知 |
| CPU 这边 | 允许 PUT/GET，并且数据块不用优化访问（[准备 CPU](#prepare-the-cpu)） | 什么都不用改：优化的数据块也能读，也不需要 PUT/GET |
| 设备发现 | 没有 | CPU 的变量（[设备发现](#discovery)） |

现在新建的设备从**按名称**开始。在有这个选项之前保存的设备，照旧**按地址**读取。切换访问方式时，点位的定型地址和变量名都会保留，所以切回去时一切都在原处。按名称时，设备标题显示的端点不带机架和槽位，例如 `s7://192.168.0.2?access=name`。不响应按名称访问的 CPU，会在设备页面上说明这一点（[没有按名称访问的 CPU](#a-cpu-without-the-access-by-name)）。

## 连接 {#connection}

| 字段 | 含义 | 默认值 |
| --- | --- | --- |
| **主机** | CPU（或者挡在它前面的那块 CP 模块）的 IP 地址或主机名。 | 空 |
| **访问** | **按地址**或**按名称**（[访问](#access)）。 | 按名称 |
| **机架 / 槽位** | 仅按地址时。CPU 的机架号和槽位号，按 Siemens 硬件组态里配置的填。同一行上两个数字框：先机架，后槽位。 | 0 / 0 |
| **用户名 / 密码** | 仅按名称时，两者都可不填：密码是 CPU 保护的密码，在它的访问级别要求时填写；用户名只用于管理用户的 CPU。在这台电脑上受保护地保存。 | 空 |
| **轮询间隔（ms）** | 整台设备的读取周期，供没有自己另作规定的点位使用。 | 1000 |

TCP 端口固定为 102（ISO-on-TCP），所以没有端口字段。设备标题栏显示拼好的 endpoint，例如 `s7://192.168.0.2:0:1`（主机、机架、槽位）。

机架和槽位必须和硬件对上：S7-1200 和 S7-1500 的 CPU 在机架 0、槽位 0 或 1 上应答；典型的 S7-300 CPU 坐在机架 0、槽位 2；S7-400 的机架随组态而变。槽位填错会让连接失败，而不是读到错的数据。

每个点位都可以设自己的**轮询间隔（ms）**；点位填 0 就退回设备的间隔。

## 按名称读取 {#read-by-name}

按名称读取的设备，其点位在“信号源”卡片的**变量名**字段里按 TIA Portal 的写法写出变量：数据块和变量，成员前面加点，数组元素放在方括号里。TIA Portal 的双引号可以照样输入（`"GanterOpt".Real_`），会被去掉。

| TIA Portal 中的变量 | 变量名 |
| --- | --- |
| 数据块 `GanterOpt` 中的 `Real_` | `GanterOpt.Real_` |
| UDT 实例 `Udt_` 的 `Valor` | `GanterOpt.Udt_.Valor` |
| 数组 `Vetor` 的元素 0 | `GanterOpt.Vetor[0]` |
| DTL `DataHora` | `GanterOpt.DataHora`：一个点位，一个日期时间 |

不用填存储区、DB 号或字节：变量在哪里、是什么类型，都由 CPU 告知，驱动把它读成点位的数据类型：

| CPU 中的类型 | 数据类型 | 说明 |
| --- | --- | --- |
| Bool | Boolean | |
| Byte、USInt；SInt | Byte；SByte | 无符号；有符号 |
| Word、UInt；Int | UInt16；Int16 | 无符号；有符号 |
| DWord、UDInt；DInt | UInt32；Int32 | 无符号；有符号 |
| LWord、ULInt；LInt | UInt64；Int64 | 全部 64 位，精确 |
| Real；LReal | Float；Double | |
| Char、String | String | Windows-1252，和按地址时一样 |
| WChar、WString | String | UTF-16，任何文字 |
| Time、S5Time | Int32 | 毫秒 |
| LTime | Int64 | 纳秒 |
| Time_Of_Day | UInt32 | 从午夜起的毫秒数，小于 24 h |
| Date、Date_And_Time、DTL、LDT | DateTime | 仅日期；日期和时间 |

设备发现给每个点位带上它的变量的数据类型和单位（ms、ns）。手工建的点位用您选的数据类型，CPU 的值会换算成它。

String 或 WString 按块中声明的长度整体写入，驱动在第一次写入前从变量读出这个长度：更长的文本会连同两个长度一起被拒绝，什么也不发送；String 拒绝 Windows-1252 以外的字符。DTL 作为一个日期时间整体读写；它的八个字段从不单独提供，因为单独写一个字段会接受任何值（包括月份 90）。超出类型范围的值（24 h 或以上的 Time_Of_Day，给 Int 的 40000）会连同范围一起被拒绝，什么也不发送。CPU 里没有的名称就是这个点位的错误，并会这样说明；单独写出 DTL 的某个字段，会得到改写 DTL 本身的建议。

### HMI 设置 {#the-hmi-settings}

TIA Portal 为数据块的每个变量保存三项设置，在块的列里。站点像 Siemens 自己的 HMI 和 OPC UA 服务器一样遵守它们，不管 CPU 放行什么：

| TIA Portal 中的设置 | 未勾选时 |
| --- | --- |
| **Accessible from HMI/OPC UA/Web API** | 这个变量既不读也不写：点位的错误会写出这项设置的名称，设备发现也不列出它。 |
| **Visible in HMI engineering** | 设备发现不列出这个变量；手工写出它的点位照常读取。 |
| **Writable from HMI/OPC UA/Web API** | 这个变量只读：写入会被拒绝并写出这项设置的名称，点位的**访问**提供只读并说明原因，设备发现把它作为只读提供。 |

### 没有按名称访问的 CPU {#a-cpu-without-the-access-by-name}

接受连接却不响应按名称访问的 CPU（固件 V2.9 之前的 S7-1500、V4.5 之前的 S7-1200、在 V17 之前的 TIA Portal 中组态的项目，或只会 S7comm 的 CPU）会让设备保持离线。设备页面用一条提示代替错误来说明这一点：CPU 需要什么，TIA Portal 在哪里显示固件（CPU 正在运行的在 **Online & diagnostics**，项目写的版本在 CPU 属性的 **General > Catalog information**），以及三条出路：

1. 升级 CPU 固件，在 TIA Portal 项目中为 CPU 设置相同的版本，并下载硬件组态。只升级固件是不够的。
2. 把**访问**设为**按地址**，允许 PUT/GET，并让数据块不用优化访问（[准备 CPU](#prepare-the-cpu)）。
3. 通过 CPU 自己的 OPC UA 服务器（S7-1500，或固件 V4.4 起的 S7-1200）用 [OPC UA](driver-opc-ua) 驱动读取。

设备的每个点位在自己的页面上也带着同一条提示，CPU 一按名称响应，提示就自动消失。诊断日志和智能体在设备的错误里读到同样的步骤。

## 准备 CPU {#prepare-the-cpu}

这一节讲的是按地址访问；按名称读取的设备一项设置都不需要。

S7-1200 或 S7-1500 只有在它自己的两项设置都到位时，才响应 S7comm 的绝对寻址。两项都在 TIA Portal 中设置，并下载到 CPU。S7-300 或 S7-400 两项都不需要。

| 设置 | 在 TIA Portal 中的位置 | 没有它时 |
| --- | --- | --- |
| 勾选 **Permit access with PUT/GET communication from remote partner** | 打开 CPU 的设备组态，选中该 CPU，然后打开 **Properties > General > Protection & Security > Connection mechanisms**。下载硬件组态（**Download to device > Hardware configuration**）：只下载程序不会带上它。 | CPU 接受连接，但拒绝每一次读和写。设备保持连接，它的点位读作质量不可用。 |
| 在工作站读取的每一个数据块上，取消勾选 **Optimized block access** | 在项目树的 **Program blocks** 下右键单击该数据块，然后打开 **Properties > General > Attributes**。编译该块并下载。之后该块的 **Offset** 列会显示每个变量的起始位置，也就是点位的**起始字节**要填的值。 | CPU 不按地址提供该块的任何字节。它的点位读作质量不可用，设备的其他点位照常读取。 |

更改 **Optimized block access** 会在下载时重新初始化该数据块：其变量回到起始值。标志位存储区（M）、输入（I）和输出（Q）始终可以寻址，无需更改。必须保持优化的数据块可以换一种方式读取：由 PLC 程序把工作站要读的值复制到一个未优化的数据块或标志位存储区，点位指向那里；或者由工作站通过 PLC 自带的 OPC UA 服务器（S7-1500；固件 4.4 起的 S7-1200），用 [OPC UA](driver-opc-ua) 驱动来读。

当 CPU 因为其中任何一项设置而拒绝时，设备页面会用一条提示说明是哪一项，并附上这些步骤；CPU 一接受，这条提示就会自行消失。对于数据块，提示会列出该块以及因此读不到值的点位。诊断日志和代理读到的是同样的步骤。CPU 中没有的数据块，以及超出数据块末尾的地址，会在指向它们的点位上照实说明。

## 寻址 {#addressing}

这一节和后面两节（数据类型、写入）讲的是按地址访问；按名称访问见[按名称读取](#read-by-name)。

S7 点位是靠定型字段寻址的，不是靠一串自由文本。面板把它们放在点位的“信号源”卡片里：

| 字段 | 含义 | 接受的取值 | 默认值 |
| --- | --- | --- | --- |
| **存储区** | 要读的存储区。 | `DataBlock`、`Merker`、`ProcessInput`、`ProcessOutput`、`Timer`、`Counter` | `DataBlock` |
| **DB 编号** | 哪一个数据块，只在“区域”是 `DataBlock` 时显示。 | 0 及以上 | 1 |
| **起始字节** | 值在这个区域里从哪个字节偏移开始。在 `Timer` 和 `Counter` 区域里，这个框改标为**定时器编号**或**计数器编号**，问的是那个定时器或计数器的编号。 | 0 及以上 | 0 |
| **位号（0–7）** | 起始字节里的位位置。只对布尔点位显示。 | 0–7 | 0 |
| **最大长度（留空为 254）** | 一个 S7 STRING 或 WSTRING 声明的最大长度，按字符计。只对 `Timer` 和 `Counter` 之外的文本点位显示。 | STRING 为 1–254，WSTRING 为 1–16382，或留空 | 留空 |
| **PLC 中的字符串类型** | 文本在块里声明的类型，与长度并列：`STRING`（每个字符一个字节，Windows-1252）或 `WSTRING`（每个字符两个字节，UTF-16，任何文字都能容纳）。只在显示最大长度的地方显示。 | `STRING`、`WSTRING` | `STRING` |

这些区域对应到 Siemens 常用的记法：`Merker` 是标志位存储区（M），`ProcessInput` 是过程映像输入（I/E），`ProcessOutput` 是过程映像输出（Q/A），`Timer`/`Counter` 是 S7-300 和 S7-400 CPU 的定时器和计数器区。定时器和计数器按编号寻址，不按字节：`Timer` 填编号 5 读的是 T5，`Counter` 填编号 3 读的是 C3。把经典的绝对地址翻过来是这样：

| 经典地址 | 数据类型 | 要填的字段 |
| --- | --- | --- |
| 存着 REAL 的 `DB5.DBD12` | Float | 区域 `DataBlock`，DB 号 5，起始字节 12 |
| `DB1.DBX3.4` | Boolean | 区域 `DataBlock`，DB 号 1，起始字节 3，位 4 |
| 存着 INT 的 `DB2.DBW20` | Int16 | 区域 `DataBlock`，DB 号 2，起始字节 20 |
| `MW10` | Int16 | 区域 `Merker`，起始字节 10 |
| `M10.3` | Boolean | 区域 `Merker`，起始字节 10，位 3 |
| `IB2` | Byte | 区域 `ProcessInput`，起始字节 2 |
| `Q4.1` | Boolean | 区域 `ProcessOutput`，起始字节 4，位 1 |
| `T5` | Int16 | 区域 `Timer`，定时器号 5 |
| `C3` | Int16 | 区域 `Counter`，计数器号 3 |

布尔点位在总线上恰好读写一个位；它绝不会把周围那个字节重写一遍。其余每一种类型都从起始字节开始按字节寻址，宽度就是它数据类型的确切宽度。“信号源”卡片里那行只读的**源地址（总线）**会随着你编辑显示拼好的地址。

## 数据类型 {#data-types}

点位的**数据类型**决定总线上那个 S7 原生类型。十三种 S7 原生宽度全都可用：

| 数据类型 | S7 类型 | 总线上的大小 |
| --- | --- | --- |
| Boolean | BOOL | 1 位 |
| SByte | SINT | 1 字节 |
| Byte | USINT | 1 字节 |
| Int16 | INT | 2 字节 |
| UInt16 | UINT | 2 字节 |
| Int32 | DINT | 4 字节 |
| UInt32 | UDINT | 4 字节 |
| Int64 | LINT | 8 字节 |
| UInt64 | ULINT | 8 字节 |
| Float | REAL | 4 字节 |
| Double | LREAL | 8 字节 |
| String | STRING 或 WSTRING | 最大长度 + 2 字节（STRING），最大长度 × 2 + 4 字节（WSTRING） |
| DateTime | DTL | 12 字节 |

多字节的值自动采用 S7 原生的大端字节序；没有字节序选项，因为根本用不着。

文本点位读的是标准的 S7 STRING 布局：一个字节的声明最大长度，一个字节的当前长度，然后是字符，每个字符一个字节，采用 Windows-1252，也就是 TIA Portal 和 Siemens HMI 显示 STRING 时用的代码页。所以工作站写入的内容就是 TIA Portal 和 HMI 显示的内容（"Peça 12" 仍是 "Peça 12"），工作站读回来的也一样。**最大长度**按字符数计，必须和块里的 STRING 声明对上（留空表示 S7 的最大值 254）；驱动每次读传输的是最大长度 + 2 个字节。日期时间点位映射到 S7-1200/1500 系列的 DTL 结构（从年一直到纳秒）；较老的 8 字节 DATE_AND_TIME 不提供。

**PLC 中的字符串类型**为 `WSTRING` 的文本点位改读 S7 WSTRING 布局：一个字的声明最大长度，一个字的当前长度，然后是字符，每个字符两个字节，采用 UTF-16，所以任何文字都能容纳（"中" 写进去，读回来仍是 "中"）。**最大长度**仍按字符数计，从 1 到 16382，必须和块里的 WString 声明对上（留空表示 254，即 TIA Portal 给不带长度声明的 WString 的长度）；驱动每次读传输的是最大长度 × 2 + 4 个字节。S7comm 无法从 CPU 读出类型，所以它必须和变量一致：对着 WString 变量却仍设为 `STRING` 的点位，会按错误的布局读写。这个字段出现之前保存的点位都是 `STRING`。

## 写入 {#writes}

**访问**为读写或只写的点位可以被写。写入寻的是和读一样的区域、字节和位：布尔写入置它那一个位，其余一切都写它类型确切的那些字节。新点位一开始是只读。

文本写入如果需要的字节数超过**最大长度**，就会被拒绝，什么也不会发送，绝不会被截短到合适的大小：诊断日志和智能体读到的是，例如 "The DB1 byte 44 write was not sent: the text takes 21 bytes, and the STRING holds 20."。长度正好等于**最大长度**的文本会被完整写入。

含有 Windows-1252 之外字符（例如汉字）的文本写入也会这样被拒绝，绝不会用问号代替该字符写进去：诊断日志和智能体读到的是，例如 "The DB1 byte 44 write was not sent: the STRING holds Windows-1252 characters only, and '中' (U+4E2D) is not one of them; it fits once the tag's text type in the PLC and the PLC variable are both WSTRING."。点位的面板会用工作站的语言给出同样的原因。

要写这样的文本，请在块里把变量声明为 WString，并把点位的**PLC 中的字符串类型**设为 `WSTRING`。WSTRING 每个字符按两个字节计，所以超过其**最大长度**的文本同样会被拒绝，什么也不会发送：例如 "The DB1 byte 94 write was not sent: the text takes 42 bytes, and the WSTRING holds 40 (20 characters of two bytes)."。

## 设备发现 {#discovery}

按名称读取的 CPU 会列出它的变量；S7comm 什么都不列，广播也找不到 CPU。在驱动页面上，**扫描范围**卡片里的**要扫描的 CPU**指定是哪台 CPU：本驱动下按名称读取其 CPU 的一台 Siemens 设备（在这台设备的主机上扫描，并用它的密码登录，受密码保护的 CPU 就是这样列出变量的），或者**在此输入的地址**，CPU 的地址填在 **CPU 地址**里。

**发现设备**把这台 CPU 作为一台设备提供，变量作为点位：提供的是 CPU 标为可从 HMI 访问且可见的变量，全部已勾选，带着 CPU 告知的类型，CPU 标为不可从 HMI 写入的是只读。列表是按每个名称的路径排成的树：数据块，然后是每个结构体或 UDT 实例和每个数组，然后是变量，每个以名称中不与其他变量重复的最短结尾命名（`Real_`、`Valor`、`Vetor[0]`）。勾选一个分支会勾选或取消勾选它下面的全部内容，分支上显示它的变量中有几个已勾选；一个 DTL 就是一个变量。**添加设备**用勾选的点位新建一台按名称读取的设备。

本站点已有 Siemens 设备按名称读取的 CPU，不管扫描是怎么指定的，就是那台设备：扫描用那台设备的密码登录，**添加所选点位**添加它还没有点位的变量。在按名称读取的设备页面上，点位卡片里的**发现点位**用同样的树只对这台设备做同样的事（见[设备发现](connector-discovery#discovering-a-devices-tags)）。按地址读取的设备不提供发现点位：它的点位请手工添加。

## 需要知道的事 {#good-to-know}

- 定时器和计数器区只存在于 S7-300 和 S7-400 的 CPU 上。S7-1200 没有这两个区，S7-1500 即使允许了 PUT/GET，也会以“Address out of range”拒绝通过 S7comm 读写这两个区；这时请在 PLC 程序里把定时器或计数器的值复制到一个数据块或标志位存储区，再从那里读。在有这两个区的 CPU 上，它们按编号寻址，不按字节偏移：那个框问的是定时器或计数器本身（T0、T1、T2 和 C0、C1、C2）。每个值是一个 16 位字，所以要配 Int16；更宽或更窄的数据类型会被拒绝，点位名写进诊断日志，而这台设备其他的点位照读不误。
- 一台够不着的 PLC 在启动时不花什么代价：设备在后台一直重试，在连接接上之前它的点位读作质量不可用。
- 设备上线之后被拔掉的线缆，在下一次读取时被发现：设备转为离线，它的点位转为质量不可用，等网络回来它自己重新连上。CPU 拒绝的一个地址不会造成这种局面，所以一个打错的点位绝不会把这台设备其余部分一起拖垮。读取时发现链路已断的点位，用工作站的语言说明与设备的连接已断开，Sharp7 的原话留在事件页面。
