目录
-
- 摘要
- 一、Modbus协议概述
-
- [1.1 什么是Modbus](#1.1 什么是Modbus)
- [1.2 Modbus特点](#1.2 Modbus特点)
- [1.3 Modbus寄存器类型](#1.3 Modbus寄存器类型)
- [二、DolphinDB Modbus插件](#二、DolphinDB Modbus插件)
-
- [2.1 插件安装](#2.1 插件安装)
- [2.2 连接配置](#2.2 连接配置)
- 三、数据读取
-
- [3.1 读取线圈](#3.1 读取线圈)
- [3.2 读取离散输入](#3.2 读取离散输入)
- [3.3 读取输入寄存器](#3.3 读取输入寄存器)
- [3.4 读取保持寄存器](#3.4 读取保持寄存器)
- 四、数据写入
-
- [4.1 写入线圈](#4.1 写入线圈)
- [4.2 写入保持寄存器](#4.2 写入保持寄存器)
- 五、数据解析
-
- [5.1 整数解析](#5.1 整数解析)
- [5.2 浮点数解析](#5.2 浮点数解析)
- [5.3 字符串解析](#5.3 字符串解析)
- 六、轮询采集
-
- [6.1 定时轮询](#6.1 定时轮询)
- [6.2 多设备轮询](#6.2 多设备轮询)
- 七、实战案例
-
- [7.1 PLC数据采集系统](#7.1 PLC数据采集系统)
- 八、总结
- 参考资料
摘要
本文深入讲解DolphinDB Modbus协议接入技术。从Modbus协议原理到插件配置,从寄存器读取到数据解析,从轮询采集到实时写入,全面介绍Modbus数据采集的核心方法。通过丰富的代码示例,帮助读者掌握PLC数据采集的核心技能。
一、Modbus协议概述
1.1 什么是Modbus
Modbus是工业领域广泛使用的通信协议:
#mermaid-svg-Ak1oOCimIXOM6Y2e{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Ak1oOCimIXOM6Y2e .error-icon{fill:#552222;}#mermaid-svg-Ak1oOCimIXOM6Y2e .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Ak1oOCimIXOM6Y2e .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .marker.cross{stroke:#333333;}#mermaid-svg-Ak1oOCimIXOM6Y2e svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Ak1oOCimIXOM6Y2e p{margin:0;}#mermaid-svg-Ak1oOCimIXOM6Y2e .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .cluster-label text{fill:#333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .cluster-label span{color:#333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .cluster-label span p{background-color:transparent;}#mermaid-svg-Ak1oOCimIXOM6Y2e .label text,#mermaid-svg-Ak1oOCimIXOM6Y2e span{fill:#333;color:#333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .node rect,#mermaid-svg-Ak1oOCimIXOM6Y2e .node circle,#mermaid-svg-Ak1oOCimIXOM6Y2e .node ellipse,#mermaid-svg-Ak1oOCimIXOM6Y2e .node polygon,#mermaid-svg-Ak1oOCimIXOM6Y2e .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Ak1oOCimIXOM6Y2e .rough-node .label text,#mermaid-svg-Ak1oOCimIXOM6Y2e .node .label text,#mermaid-svg-Ak1oOCimIXOM6Y2e .image-shape .label,#mermaid-svg-Ak1oOCimIXOM6Y2e .icon-shape .label{text-anchor:middle;}#mermaid-svg-Ak1oOCimIXOM6Y2e .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Ak1oOCimIXOM6Y2e .rough-node .label,#mermaid-svg-Ak1oOCimIXOM6Y2e .node .label,#mermaid-svg-Ak1oOCimIXOM6Y2e .image-shape .label,#mermaid-svg-Ak1oOCimIXOM6Y2e .icon-shape .label{text-align:center;}#mermaid-svg-Ak1oOCimIXOM6Y2e .node.clickable{cursor:pointer;}#mermaid-svg-Ak1oOCimIXOM6Y2e .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .arrowheadPath{fill:#333333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Ak1oOCimIXOM6Y2e .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ak1oOCimIXOM6Y2e .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Ak1oOCimIXOM6Y2e .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ak1oOCimIXOM6Y2e .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Ak1oOCimIXOM6Y2e .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Ak1oOCimIXOM6Y2e .cluster text{fill:#333;}#mermaid-svg-Ak1oOCimIXOM6Y2e .cluster span{color:#333;}#mermaid-svg-Ak1oOCimIXOM6Y2e div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Ak1oOCimIXOM6Y2e .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Ak1oOCimIXOM6Y2e rect.text{fill:none;stroke-width:0;}#mermaid-svg-Ak1oOCimIXOM6Y2e .icon-shape,#mermaid-svg-Ak1oOCimIXOM6Y2e .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Ak1oOCimIXOM6Y2e .icon-shape p,#mermaid-svg-Ak1oOCimIXOM6Y2e .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Ak1oOCimIXOM6Y2e .icon-shape .label rect,#mermaid-svg-Ak1oOCimIXOM6Y2e .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Ak1oOCimIXOM6Y2e .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Ak1oOCimIXOM6Y2e .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Ak1oOCimIXOM6Y2e :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Modbus架构
主站
DolphinDB
从站1
PLC
从站2
传感器
从站3
仪表
通信方式
Modbus TCP
Modbus RTU
1.2 Modbus特点
| 特点 | 说明 |
|---|---|
| 简单可靠 | 协议简单,稳定可靠 |
| 广泛应用 | 工业标准协议 |
| 多种传输 | TCP/RTU/ASCII |
| 开放免费 | 无授权费用 |
1.3 Modbus寄存器类型
| 类型 | 地址范围 | 访问 | 说明 |
|---|---|---|---|
| 线圈 | 00001-09999 | 读写 | 开关量输出 |
| 离散输入 | 10001-19999 | 只读 | 开关量输入 |
| 输入寄存器 | 30001-39999 | 只读 | 模拟量输入 |
| 保持寄存器 | 40001-49999 | 读写 | 模拟量输出 |
二、DolphinDB Modbus插件
2.1 插件安装
python
// 检查插件
getLoadedPlugins()
// 加载Modbus插件
loadPlugin("modbus")
// 查看插件函数
modbus::getPluginFunctions()
2.2 连接配置
python
// Modbus TCP连接
conn = modbus::connectTcp("192.168.1.100", 502)
// Modbus RTU连接(串口)
conn = modbus::connectRtu("/dev/ttyUSB0", 9600, "N", 8, 1)
// 参数:串口、波特率、校验位、数据位、停止位
三、数据读取
3.1 读取线圈
python
// 连接Modbus设备
conn = modbus::connectTcp("192.168.1.100", 502)
// 设置从站ID
modbus::setSlaveId(conn, 1)
// 读取单个线圈(地址0)
coil = modbus::readCoil(conn, 0, 1)
print("线圈状态: " + string(coil[0]))
// 读取多个线圈(地址0-9)
coils = modbus::readCoil(conn, 0, 10)
print("线圈状态: " + string(coils))
3.2 读取离散输入
python
// 读取离散输入(地址10001对应地址0)
inputs = modbus::readDiscreteInput(conn, 0, 8)
print("离散输入: " + string(inputs))
3.3 读取输入寄存器
python
// 读取单个输入寄存器
value = modbus::readInputRegister(conn, 0, 1)
print("输入寄存器值: " + string(value[0]))
// 读取多个输入寄存器
values = modbus::readInputRegister(conn, 0, 10)
print("输入寄存器值: " + string(values))
3.4 读取保持寄存器
python
// 读取保持寄存器
values = modbus::readHoldingRegister(conn, 0, 10)
print("保持寄存器值: " + string(values))
四、数据写入
4.1 写入线圈
python
// 写入单个线圈
modbus::writeSingleCoil(conn, 0, true) // 打开
modbus::writeSingleCoil(conn, 0, false) // 关闭
// 写入多个线圈
modbus::writeMultipleCoils(conn, 0, [true, false, true, false])
4.2 写入保持寄存器
python
// 写入单个保持寄存器
modbus::writeSingleRegister(conn, 0, 100)
// 写入多个保持寄存器
modbus::writeMultipleRegisters(conn, 0, [100, 200, 300])
五、数据解析
5.1 整数解析
python
// 16位整数解析
def parseInt16(values, offset = 0) {
return values[offset]
}
// 32位整数解析(两个寄存器)
def parseInt32(values, offset = 0) {
return values[offset] * 65536 + values[offset + 1]
}
// 有符号整数
def parseSignedInt16(values, offset = 0) {
v = values[offset]
return iif(v >= 32768, v - 65536, v)
}
5.2 浮点数解析
python
// 32位浮点数解析(IEEE 754)
def parseFloat32(values, offset = 0) {
// 需要将两个16位寄存器转换为32位浮点数
// 使用位操作
high = values[offset]
low = values[offset + 1]
// 转换逻辑
return float32fromBits(high * 65536 + low)
}
5.3 字符串解析
python
// 字符串解析
def parseString(values, length) {
chars = array(CHAR, length)
for (i in 0..length) {
chars[i] = char(values[i / 2] >> (8 * (i % 2)))
}
return concat(chars)
}
六、轮询采集
6.1 定时轮询
python
// 创建流表
share streamTable(100000:0,
`device_id`timestamp`temperature`pressure`flow,
[SYMBOL, TIMESTAMP, DOUBLE, DOUBLE, DOUBLE]) as modbus_stream
// 轮询函数
def pollModbus(conn, slaveId, interval = 1000) {
modbus::setSlaveId(conn, slaveId)
while (true) {
try {
// 读取寄存器
values = modbus::readInputRegister(conn, 0, 6)
// 解析数据
temperature = values[0] / 10.0
pressure = values[1] / 10.0
flow = values[2] / 10.0
// 写入流表
insert into modbus_stream
values ("device_" + string(slaveId), now(), temperature, pressure, flow)
} catch (ex) {
print("读取失败: " + ex)
}
sleep(interval)
}
}
// 启动轮询
submitJob("modbus_poll", "Modbus轮询",
def() { pollModbus(conn, 1, 1000) })
6.2 多设备轮询
python
// 多设备轮询
def pollMultipleDevices(conn, slaveIds, interval = 1000) {
while (true) {
for (slaveId in slaveIds) {
try {
modbus::setSlaveId(conn, slaveId)
values = modbus::readInputRegister(conn, 0, 3)
temperature = values[0] / 10.0
pressure = values[1] / 10.0
flow = values[2] / 10.0
insert into modbus_stream
values ("device_" + string(slaveId), now(), temperature, pressure, flow)
} catch (ex) {
print("设备 " + string(slaveId) + " 读取失败")
}
}
sleep(interval)
}
}
// 启动多设备轮询
submitJob("modbus_multi_poll", "多设备轮询",
def() { pollMultipleDevices(conn, [1, 2, 3, 4, 5], 1000) })
七、实战案例
7.1 PLC数据采集系统
python
// ========== PLC数据采集系统 ==========
// 1. 创建分布式表
db = database("dfs://modbus_db", VALUE, 1..100)
schema = table(1:0,
`device_id`timestamp`temperature`pressure`flow_rate`level,
[SYMBOL, TIMESTAMP, DOUBLE, DOUBLE, DOUBLE, DOUBLE])
db.createPartitionedTable(schema, `plc_data, `device_id)
// 2. 创建流表
share streamTable(100000:0,
`device_id`timestamp`temperature`pressure`flow_rate`level,
[SYMBOL, TIMESTAMP, DOUBLE, DOUBLE, DOUBLE, DOUBLE]) as modbus_stream
// 3. 启用持久化
enableTablePersistence(modbus_stream, true, true, 1000000)
// 4. 订阅流表写入分布式表
subscribeTable(, "modbus_stream", "persist", -1,
def(msg) {
loadTable("dfs://modbus_db", "plc_data").append!(msg)
}, 10000, 5000)
// 5. 连接Modbus设备
conn = modbus::connectTcp("192.168.1.100", 502)
// 6. 启动轮询
submitJob("plc_poll", "PLC轮询",
def() {
modbus::setSlaveId(conn, 1)
while (true) {
try {
values = modbus::readInputRegister(conn, 0, 8)
insert into modbus_stream values (
"PLC_001",
now(),
values[0] / 10.0, // 温度
values[1] / 10.0, // 压力
values[2] / 10.0, // 流量
values[3] / 10.0 // 液位
)
} catch (ex) {
print("读取失败: " + ex)
}
sleep(1000)
}
})
// 7. 监控
def monitorModbus() {
print("=== Modbus监控 ===")
print("流表行数: " + string(exec count(*) from modbus_stream))
print("最新数据: ")
select top 5 * from modbus_stream
}
monitorModbus()
print("PLC数据采集系统启动完成")
八、总结
本文详细介绍了DolphinDB Modbus协议接入:
- Modbus协议:寄存器类型、通信方式
- 插件配置:TCP连接、RTU连接
- 数据读取:线圈、离散输入、寄存器
- 数据写入:线圈写入、寄存器写入
- 数据解析:整数、浮点数、字符串
- 轮询采集:定时轮询、多设备轮询
思考题:
- Modbus TCP和RTU有什么区别?
- 如何设计高效的Modbus轮询策略?
- 如何处理Modbus通信异常?