[分享]go开发modbus-rtu通信
本帖最后由 Baldwin 于 2020-3-4 22:25 编辑用到的gomodbus包是坛友slzm40编写和共享的,感谢他的无私分享,本帖只是抛砖引玉,将我自己学习的一些经历分享给大家;
有关modbus包可以直接向slzm40请教或一起讨论;
1、创建工程,导入modbus包
包地址"github.com/thinkgos/gomodbus";
2、打开虚拟串口
添加一对虚拟串口
图2.1
3、打开modbus-slave软件
打开modbus-slave从机软件,用来测试从机;
需要注意设置通信地址和读取寄存器的地址和数量;
图3.1
4、运行代码
查看可用串口,因为虚拟串口选择com1和com2,虚拟中我选择使用/dev/ttyS1
图4.1
编译运行,会提示串口打开失败,这是因为linux对设备的权限做了限制
图4.2
获取串口读写、运行等权限
图4.3
5、测试ok
图5.1
到此我们已经能正确读取03寄存器的值;其他功能可以自行测试;
package main
import (
"fmt"
modbus "github.com/thinkgos/gomodbus"
"github.com/thinkgos/gomodbus/mb"
"time"
)
func main(){
//调用RTUClientProvider的构造函数,返回结构体指针
p := modbus.NewRTUClientProvider()
p.Address = "/dev/ttyS1"
p.BaudRate = 115200
p.DataBits = 8
p.Parity = "N"
p.StopBits = 1
p.Timeout = 100 * time.Millisecond
client := mb.NewClient(p)
client.LogMode(true)
err := client.Start()
if err != nil {
fmt.Println("start err,", err)
return
}
for {
value, err := client.ReadHoldingRegisters(1, 1, 3)
if err != nil {
fmt.Println("readHoldErr,", err)
} else {
fmt.Printf("%#v\n", value)
}
time.Sleep(time.Second * 3)
}
}
来捧一下场先,在win环境下p.Address = "/dev/ttyS1" 这个怎样填"com1" ? daiqx 发表于 2020-3-5 09:19
来捧一下场先,在win环境下p.Address = "/dev/ttyS1" 这个怎样填"com1" ?
一般是com1,但是可能需要区分大小写,你试一下先;之前搞qt的时候是按照设备管理器的名称来弄的 Baldwin 发表于 2020-3-5 10:19
一般是com1,但是可能需要区分大小写,你试一下先;之前搞qt的时候是按照设备管理器的名称来弄的 ...
请问下楼主有没试过他的Modbus TCP Server例子, 我试了下不会填寄存器的值呀,刚学go,不太懂。
srv.AddNodes(modbus.NewNodeRegister(1,
0, 0, 0, 0,
0, 0, 0, 10))
这里我注册了10个寄存器地址,我现在不知道怎么改寄存器的值,比如把温湿度传感器的值填到寄存器里让TCP Client来获取数据。 lghtjpu 发表于 2020-3-9 16:23
请问下楼主有没试过他的Modbus TCP Server例子, 我试了下不会填寄存器的值呀,刚学go,不太懂。
srv.Ad ...
我没有用tcpSever,刚看了源代码,这个NodeRegister的构造函数NewNodeRegister返回的是NodeRegister结构体指针,应该调用它的写保持寄存器方法应该就可以设置寄存器的值了;
-------------构造函数--------------
// NewNodeRegister 创建一个modbus子节点寄存器列表
func NewNodeRegister(slaveID byte,
coilsAddrStart, coilsQuantity,
discreteAddrStart, discreteQuantity,
inputAddrStart, inputQuantity,
holdingAddrStart, holdingQuantity uint16) *NodeRegister {
coilsBytes := (int(coilsQuantity) + 7) / 8
discreteBytes := (int(discreteQuantity) + 7) / 8
b := make([]byte, coilsBytes+discreteBytes)
w := make([]uint16, int(inputQuantity)+int(holdingQuantity))
return &NodeRegister{
slaveID: slaveID,
coilsAddrStart: coilsAddrStart,
coilsQuantity: coilsQuantity,
coils: b[:coilsBytes],
discreteAddrStart: discreteAddrStart,
discreteQuantity:discreteQuantity,
discrete: b,
inputAddrStart: inputAddrStart,
input: w[:inputQuantity],
holdingAddrStart:holdingAddrStart,
holding: w,
}
}
------------------写保持寄存器方法--------------
// WriteHoldings 写保持寄存器
func (sf *NodeRegister) WriteHoldings(address uint16, valBuf []uint16) error {
quality := uint16(len(valBuf))
sf.rw.Lock()
if (address >= sf.holdingAddrStart) &&
((address + quality) <= (sf.holdingAddrStart + uint16(len(sf.holding)))) {
start := address - sf.holdingAddrStart
end := start + quality
copy(sf.holding, valBuf)
sf.rw.Unlock()
return nil
}
sf.rw.Unlock()
return &ExceptionError{ExceptionCodeIllegalDataAddress}
}
WriteHoldings这个方法有2个参数,寄存器地址和切片类型的寄存器值;
函数内不通过copy进行了赋值拷贝
copy(sf.holding, valBuf)
你先试试看 Baldwin 发表于 2020-3-9 22:20
我没有用tcpSever,刚看了源代码,这个NodeRegister的构造函数NewNodeRegister返回的是NodeRegister结构 ...
这个我上午试过,貌似不行呢,我的代码是这样写的:
1,定义了两个全局的变量
var buff []byte = []byte{11, 22, 33, 44, 55, 66, 77, 88, 99, 119} //写入寄存器的值
var mbData *modbus.NodeRegister
2.注册了10个寄存器后把这个结构后取到这个结构的指针
srv.AddNodes(modbus.NewNodeRegister(1,
0, 0, 0, 0,
0, 0, 0, 10))
mbData, _ = srv.GetNode(1)
3.然后使用WriteHoldingsBytes()来写入值
mbData.WriteHoldingsBytes(0, 10, buff)
结果没出来,不知道哪里出了问题 lghtjpu 发表于 2020-3-9 22:49
这个我上午试过,貌似不行呢,我的代码是这样写的:
1,定义了两个全局的变量
var buff []byte = []byte{ ...
行参类型不一样吧,你定义的是byte类型的切片,函数需要的是uint16类型的切片 Baldwin 发表于 2020-3-10 08:47
行参类型不一样吧,你定义的是byte类型的切片,函数需要的是uint16类型的切片 ...
我之前是用 WriteHoldingsBytes()方法,刚试了换了你说的 WriteHoldings()就可以了 原来之前用WriteHoldingsBytes()不行是因为长度那需要是切片长度的一半{:dizzy:},没太细看库的代码,吃亏了 本帖最后由 slzm40 于 2020-3-10 21:00 编辑
lghtjpu 发表于 2020-3-10 10:48
原来之前用WriteHoldingsBytes()不行是因为长度那需要是切片长度的一半,没太细看库的代码,吃 ...
保持寄存器本来就是16位的. 你按字节,切片的长度应该是你要输入数量的2倍. 不过你这个让我发现了个bug,client端没检查这两个输入值{:sweat:} slzm40 发表于 2020-3-10 20:50
保持寄存器本来就是16位的. 你按字节,切片的长度应该是你要输入数量的2倍. 不过你这个让我发现了个bug,cl ...
对于RTU来说你这个Serial client其实是主站吧, 之前看github上的说明以为不支持Serial 主站呢 lghtjpu 发表于 2020-3-10 23:16
对于RTU来说你这个Serial client其实是主站吧, 之前看github上的说明以为不支持Serial 主站呢 ...
client 和主站是一个概念吧... 一般统称主询的都是client. 被动等询都属server.
modbus才说主站从站.
页:
[1]