Baldwin 发表于 2020-3-4 22:23:01

[分享]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)
        }
}

daiqx 发表于 2020-3-5 09:19:42

来捧一下场先,在win环境下p.Address = "/dev/ttyS1" 这个怎样填"com1" ?

Baldwin 发表于 2020-3-5 10:19:49

daiqx 发表于 2020-3-5 09:19
来捧一下场先,在win环境下p.Address = "/dev/ttyS1" 这个怎样填"com1" ?

一般是com1,但是可能需要区分大小写,你试一下先;之前搞qt的时候是按照设备管理器的名称来弄的

lghtjpu 发表于 2020-3-9 16:23:58

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来获取数据。

Baldwin 发表于 2020-3-9 22:20:57

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)
你先试试看

lghtjpu 发表于 2020-3-9 22:49:59

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)

结果没出来,不知道哪里出了问题

Baldwin 发表于 2020-3-10 08:47:19

lghtjpu 发表于 2020-3-9 22:49
这个我上午试过,貌似不行呢,我的代码是这样写的:
1,定义了两个全局的变量
var buff []byte = []byte{ ...

行参类型不一样吧,你定义的是byte类型的切片,函数需要的是uint16类型的切片

lghtjpu 发表于 2020-3-10 10:25:29

Baldwin 发表于 2020-3-10 08:47
行参类型不一样吧,你定义的是byte类型的切片,函数需要的是uint16类型的切片 ...

我之前是用 WriteHoldingsBytes()方法,刚试了换了你说的 WriteHoldings()就可以了

lghtjpu 发表于 2020-3-10 10:48:10

原来之前用WriteHoldingsBytes()不行是因为长度那需要是切片长度的一半{:dizzy:},没太细看库的代码,吃亏了

slzm40 发表于 2020-3-10 20:50:29

本帖最后由 slzm40 于 2020-3-10 21:00 编辑

lghtjpu 发表于 2020-3-10 10:48
原来之前用WriteHoldingsBytes()不行是因为长度那需要是切片长度的一半,没太细看库的代码,吃 ...

保持寄存器本来就是16位的. 你按字节,切片的长度应该是你要输入数量的2倍. 不过你这个让我发现了个bug,client端没检查这两个输入值{:sweat:}

lghtjpu 发表于 2020-3-10 23:16:12

slzm40 发表于 2020-3-10 20:50
保持寄存器本来就是16位的. 你按字节,切片的长度应该是你要输入数量的2倍. 不过你这个让我发现了个bug,cl ...

对于RTU来说你这个Serial client其实是主站吧, 之前看github上的说明以为不支持Serial 主站呢

slzm40 发表于 2020-3-11 08:16:26

lghtjpu 发表于 2020-3-10 23:16
对于RTU来说你这个Serial client其实是主站吧, 之前看github上的说明以为不支持Serial 主站呢 ...

client 和主站是一个概念吧... 一般统称主询的都是client. 被动等询都属server.
modbus才说主站从站.
页: [1]
查看完整版本: [分享]go开发modbus-rtu通信