本项目为雷赛 SC3U-60A12D PLC 提供独立的 C++17 Modbus TCP 读取核心模块。它用于上位机读取 PLC 的本体输入、M 辅助继电器和 D 字寄存器;后续 Qt 客户端只需链接 sc3u_modbus,无需依赖 QtNetwork。
安全边界:项目没有实现 Modbus 写线圈或写寄存器功能,不会经由网络改变 PLC 状态、输出或运动控制命令。
Qt 客户端 / 控制台程序
│ 调用 C++ 接口
▼
sc3u_modbus(持久 TCP 连接、报文校验、地址解析)
│ Modbus TCP,只读功能码 01 / 02 / 03 / 04
▼
SC3U-60A12D(Modbus TCP 从站)
│
├─ X:现场传感器与开关量输入
├─ M:PLC 内部布尔状态
└─ D:PLC 内部 16 位数值
核心模块只保留以下读取功能:
| 方法 | Modbus 功能码 | 读取对象 |
|---|---|---|
readCoils / readMbits |
0x01 |
M、Y、B、S 等线圈;本项目优先封装 M |
readDiscreteInputs / readXbits |
0x02 |
X 本体输入 |
readHoldingRegisters / readDwords |
0x03 |
D、R 字寄存器;本项目优先封装 D |
readInputRegisters |
0x04 |
输入寄存器 |
LeadStudio V3.1 默认采用“通讯地址兼容模式一”。此模式下软元件编号并不总是等于 Modbus 地址,模块通过 AddressResolver 统一换算,禁止调用方自行猜测偏移。
| PLC 变量 | 截图中的地址 | 功能码 | 模式一 Modbus 地址 | 说明 |
|---|---|---|---|---|
RC_FeedRightRev |
M721 |
0x01 |
0x0AD1 |
布尔状态 |
RC_FeedRightValue |
D610 |
0x03 |
0x1262 |
INT,有符号 16 位 |
RC_FeedLeftValue |
D611 |
0x03 |
0x1263 |
INT,有符号 16 位 |
| 本体输入 | X0 至 X43 |
0x02 |
0x0400 起连续 36 位 |
传感器/开关量输入 |
模式一存在不连续区间:M1535 到 M1536、D4095 到 D4096 之间不能用一条 Modbus 请求连续读取。核心模块会主动拒绝跨越这些边界的高层 readMbits、readDwords 请求,调用方应拆分为两次读取。
若 PLC 工程切换到“模式二”,调用时必须显式传入 sc3u::AddressMode::ModeTwo。例如:
const auto flags = client.readMbits(721, 1, sc3u::AddressMode::ModeTwo);cmake -S . -B build -G Ninja -DCMAKE_CXX_COMPILER=g++ -DCMAKE_BUILD_TYPE=Debug
cmake --build build --parallel 2
ctest --test-dir build --output-on-failure在“x64 Native Tools Command Prompt for VS”中执行:
cmake -S . -B build-msvc -G Ninja -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=Debug
cmake --build build-msvc --parallel 2
ctest --test-dir build-msvc --output-on-failure先确认 PLC IP,再运行:
.\build-msvc\sc3u_read_demo.exe 192.168.1.3示例会建立一条 TCP 连接,并只读输出:
X0至X43的 36 路本体输入;M721,即截图中的RC_FeedRightRev;D610、D611,即截图中的两个INT数值变量。
192.168.1.3 只是硬件手册中的 eth0 出厂地址;实际运行必须以现场配置为准。
每个 sc3u::Client 默认将详细通信日志输出到控制台,包含连接、M/X/D 地址映射、Modbus 请求/响应帧、事务号、响应校验、异常和断线。原始报文以十六进制输出,便于将上位机日志与抓包结果对照。
控制台输出无需额外配置:
sc3u::Client client({"192.168.1.3", 502, 1, std::chrono::milliseconds{3000}});仅写入指定文件:
sc3u::Endpoint endpoint{"192.168.1.3", 502, 1, std::chrono::milliseconds{3000}};
endpoint.logging.destination = sc3u::LogDestination::File;
endpoint.logging.filePath = "log/plc.log";
sc3u::Client client(std::move(endpoint));日志文件的父目录不存在时会自动创建;文件以追加方式写入,不会覆盖既有现场记录。
同时写入控制台与文件:
endpoint.logging.destination = sc3u::LogDestination::ConsoleAndFile;若高频轮询导致日志量过大,可保留连接和错误日志,但关闭原始报文输出:
endpoint.logging.includeRawFrames = false;核心库没有 Qt 依赖。Qt 工程使用 CMake 时,可将本项目作为子目录并链接目标:
add_subdirectory(path/to/upper)
target_link_libraries(your_qt_target PRIVATE sc3u_modbus)在 Qt 的工作线程中创建并使用一个 sc3u::Client。同一个实例不要跨线程共享:它维护持久 TCP 连接和递增事务编号,跨线程并发调用会破坏请求与响应的对应关系。
sc3u::Client client({"192.168.1.3", 502, 1, std::chrono::milliseconds{3000}});
const auto sensors = client.readXbits("X0", 36);
const auto enabled = client.readMbits(721, 1);
const auto feedValues = client.readDint16(610, 2);- 在 LeadStudio 的“设备 -> 系统配置”中确认“通讯地址兼容模式”为“模式一”。
- 在
Ethernet下添加并启用ModbusTcpSlave,PLC 作为 Modbus TCP 服务端。 - 确认 PLC 的实际 IP;客户端默认使用端口
502、站号1。 - 将上位机网卡设置为与 PLC 相同网段,且不与任何现有设备冲突。
- 先在 LeadStudio 在线监控中确认目标 X/M/D 变量确实随现场传感器变化;通信模块只能读取 PLC 当前已采集的数据。
- 原因:核心层不绑定 Qt,Qt Widgets、Qt Quick、命令行工具或服务程序都可复用。
- 优势:没有第三方运行时依赖;使用持久 TCP 连接,避免高频轮询时反复握手;对 MBAP 长度、事务编号、站号、功能码和响应字节数逐项校验;默认详细日志便于现场定位。
- 风险:地址兼容模式或 Modbus 从站未正确配置时,会出现超时或非法地址异常;高频轮询时保留原始报文会产生较多日志;现场网络应与办公网络隔离,避免非授权设备访问 PLC。
- 超时或连接失败:检查网线、交换机、电脑与 PLC 网段、PLC IP、端口
502与 Modbus TCP 从站使能状态。 - Modbus 异常码:检查地址兼容模式、功能码与软元件类型是否匹配,以及是否跨越模式一的地址断层。
- 能读取但数值不对:确认变量数据类型;
D610是有符号INT,模拟量还需要按 PLC 程序的量程和比例换算。 - 输入不变化:先在 LeadStudio 中确认对应 X 输入灯、变量或程序映射是否变化,再检查上位机读取。
以下验证已由本地自动执行:
- MinGW 与 MSVC 均可编译;
- CTest 覆盖地址映射、M/D 地址断层、请求帧、位/字解析和 Modbus 异常响应;
- 回环模拟 PLC 覆盖真实 TCP 连接、
M721 -> 0x0AD1请求及响应解析; - 源文件为 UTF-8 无 BOM,公开接口不包含 Modbus 写功能码。
真实 PLC 尚未连接到开发环境,因此现场仍需执行“运行最小示例”完成最终联机验证。
- LeadStudio 编程及应用手册:用于确认通讯地址兼容模式、M/D/X 地址表、Modbus TCP 功能码和从站默认端口。
- SC3U 系列运动控制小型 PLC 用户手册(硬件篇):用于确认 SC3U-60A12D 的本体 I/O 规模与以太网硬件能力。