欢迎来到青少年创客营 S7 协议实战训练营!
在第一阶段,我们已经搭建了"作战室"(环境),认识了"对手"(PLC),并画好了"藏宝图"(寻址)。
现在,我们进入第二阶段:核心实战 ------ 使用 s7connector 库。
在这个阶段,我们将使用一个纯 Java 实现 的轻量级开源库 ------ s7connector。它不需要像 Snap7 那样配置 DLL 文件,非常适合 Java 开发者快速上手。我们将通过它去"触碰"PLC 内部的变量。
🎯 第二阶段目标
- 引入武器 :在 Maven 项目中集成
s7connector。 - 建立连接:理解 S7 连接所需的"机架号"和"槽号"。
- 数据读写:使用现代化的 Java 代码实现数据的读取与控制。
- 避坑指南:解决最让人头疼的"数据乱码"问题(字节序)。
🛠️ 模块一:引入"武器库" (s7connector)
s7connector 是一个纯 Java 编写的库,它的特点是轻量 、无依赖,并且对 S7 协议的底层细节封装得比较优雅。
1. 添加 Maven 依赖
打开你的 pom.xml 文件,加入以下依赖:
xml
<dependency>
<groupId>com.github.s7connector</groupId>
<artifactId>s7connector</artifactId>
<version>1.1.0</version> <!-- 版本号请以 Maven 中央仓库最新为准 -->
</dependency>
2. 理解核心概念:机架与槽
与普通的 TCP 连接不同,S7 协议在建立连接时,除了 IP 地址,还需要告诉 PLC 你的"位置"。
- Rack (机架号) :PLC 安装在哪个机架上,通常默认为
0。 - Slot (槽号) :CPU 插在机架的哪个位置。
- S7-1200/1500 :槽号通常是
1(或者是 0,取决于具体固件,创客营常用 1)。 - S7-300/400 :槽号通常是
2。
- S7-1200/1500 :槽号通常是
创客提示:如果连接报错,首先检查槽号是否正确!
🔌 模块二:建立连接 ------ "握手"
在 s7connector 中,我们使用构建者模式来创建连接。
1. 代码实现
创建一个 PlcClient.java 类:
java
import com.github.s7connector.S7Connector;
import com.github.s7connector.S7ConnectorFactory;
import com.github.s7connector.impl.S7ConnectorImpl;
public class PlcClient {
public static void main(String[] args) {
// 1. 构建连接器
// 假设 PLC IP 为 192.168.1.10,机架 0,槽 1 (S7-1200 常见配置)
S7Connector connector = S7ConnectorFactory.buildTCPConnector()
.withHost("192.168.1.10")
.withRack(0)
.withSlot(1)
.build();
try {
// 2. 建立连接
connector.connect();
System.out.println("✅ 连接成功!PLC 正在等待指令...");
// 3. 进行读写操作...
} catch (Exception e) {
System.err.println("❌ 连接失败:" + e.getMessage());
// 常见错误:Connection refused (检查 IP)
// 常见错误:ISO-on-TCP connection failed (检查机架/槽号)
}
}
}
📖 模块三:读取数据 ------ "读取藏宝图"
假设我们在 PLC 的 DB1 中定义了以下变量(标准访问模式):
| 地址 | 变量名 | 类型 | 说明 |
|---|---|---|---|
DB1.DBX0.0 |
StartBtn |
Bool | 启动按钮状态 |
DB1.DBD2 |
Temperature |
Real | 当前温度 (浮点数) |
DB1.DBW6 |
Speed |
Int | 电机转速 |
1. Java 读取代码
s7connector 的 API 设计非常直观,直接对应数据类型。
java
// 读取布尔量 (Bit)
// 参数格式:"DB{块号}.DBX{字节}.{位}"
boolean isStart = connector.readBoolean("DB1.DBX0.0");
System.out.println("启动按钮状态: " + isStart);
// 读取浮点数 (Real) - 对应 DBD
// 参数格式:"DB{块号}.DBD{字节}"
float temp = connector.readFloat("DB1.DBD2");
System.out.println("当前温度: " + temp + " °C");
// 读取整数 (Int) - 对应 DBW
// 参数格式:"DB{块号}.DBW{字节}"
int speed = connector.readShort("DB1.DBW6"); // 注意:S7 的 Int 是 16位,对应 Java 的 short
System.out.println("电机转速: " + speed + " rpm");
注意 :S7 协议中的
INT类型是 16 位有符号整数,在 Java 中对应short。如果你用readInt()(32位),读出来的数值会错误。
✍️ 模块四:写入数据 ------ "下达指令"
控制 PLC 就像给变量赋值一样简单。
1. Java 写入代码
假设我们要强制启动电机,并设置温度报警阈值为 85.5。
java
// 写入布尔量:启动
connector.writeBoolean("DB1.DBX0.0", true);
System.out.println("✅ 已发送启动指令");
// 写入浮点数:设置阈值
connector.writeFloat("DB1.DBD10", 85.5f);
System.out.println("✅ 温度阈值已更新");
2. 写入原理
s7connector 会自动将你的 Java 数据类型转换成 S7 协议的字节流(PDU),并通过 0x05 (Write) 功能码发送给 PLC。
⚠️ 模块五:避坑指南 ------ "字节序之谜"
这是创客营里**90%**的新手会遇到的问题:为什么读出来的数字大得离谱?
1. 现象
PLC 里 Temperature 是 25.0,Java 读出来却是 1.23E-40 或者一个巨大的整数。
2. 原因:大端 vs 小端
- PLC (西门子) :使用大端模式 (Big-Endian)。高位字节在前,低位字节在后。
- 例如
0x12345678在网线上是12 34 56 78。
- 例如
- PC (Intel/Java) :使用小端模式 (Little-Endian)。低位字节在前,高位字节在后。
- Java 期望的顺序是
78 56 34 12。
- Java 期望的顺序是
3. 解决方案
s7connector 作为一个优秀的库,内部已经自动处理了字节序转换。
- 当你调用
readFloat()或readShort()时,它会在底层自动把大端数据翻转成小端数据。 - 只要你使用类型化的方法(如
readFloat),就不需要手动处理字节序。
警告 :如果你直接使用
read(byte[])读取原始字节,那么你拿到的依然是大端数据,需要自己手动翻转!
📝 第二阶段实战任务
在完成第二阶段学习后,请尝试完成以下挑战:
- 数据监控器 :
- 编写一个 Java 循环,每隔 1 秒读取一次
DB1.DBD2(温度)。 - 如果温度 > 50.0,在控制台打印 "⚠️ 高温报警!"。
- 编写一个 Java 循环,每隔 1 秒读取一次
- 远程控制 :
- 在控制台输入
1,Java 程序向 PLC 写入true(启动)。 - 输入
0,写入false(停止)。
- 在控制台输入
- 验证 :
- 打开 TIA Portal 的"监控表",观察你写入的值是否真的改变了 PLC 里的状态。
准备好了吗?
当你成功读写数据的那一刻,你就已经打破了物理世界(PLC)和数字世界(Java)的界限。
接下来,我们将进入第三阶段:最佳实践,学习如何让程序更稳定、更专业,不再"动不动就崩溃"。