搜索
bottom↓
回复: 11

[分享]go开发modbus-rtu通信

[复制链接]

出0入0汤圆

发表于 2020-3-4 22:23:01 | 显示全部楼层 |阅读模式
本帖最后由 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寄存器的值;其他功能可以自行测试;


  1. package main

  2. import (
  3.         "fmt"
  4.         modbus "github.com/thinkgos/gomodbus"
  5.         "github.com/thinkgos/gomodbus/mb"
  6.         "time"
  7. )

  8. func main(){
  9.         //调用RTUClientProvider的构造函数,返回结构体指针
  10.         p := modbus.NewRTUClientProvider()
  11.         p.Address = "/dev/ttyS1"
  12.         p.BaudRate = 115200
  13.         p.DataBits = 8
  14.         p.Parity = "N"
  15.         p.StopBits = 1
  16.         p.Timeout = 100 * time.Millisecond

  17.         client := mb.NewClient(p)
  18.         client.LogMode(true)
  19.         err := client.Start()
  20.         if err != nil {
  21.                 fmt.Println("start err,", err)
  22.                 return
  23.         }

  24.         for {
  25.                 value, err := client.ReadHoldingRegisters(1, 1, 3)
  26.                 if err != nil {
  27.                         fmt.Println("readHoldErr,", err)
  28.                 } else {
  29.                         fmt.Printf("%#v\n", value)
  30.                 }

  31.                 time.Sleep(time.Second * 3)
  32.         }
  33. }
复制代码

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

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

出0入0汤圆

 楼主| 发表于 2020-3-5 10:19:49 | 显示全部楼层
daiqx 发表于 2020-3-5 09:19
来捧一下场先,在win环境下p.Address = "/dev/ttyS1" 这个怎样填  "com1" ?

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

出0入0汤圆

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

出0入0汤圆

 楼主| 发表于 2020-3-9 22:20:57 | 显示全部楼层
lghtjpu 发表于 2020-3-9 16:23
请问下楼主有没试过他的Modbus TCP Server例子, 我试了下不会填寄存器的值呀,刚学go,不太懂。

srv.Ad ...


我没有用tcpSever,刚看了源代码,这个NodeRegister的构造函数NewNodeRegister返回的是NodeRegister结构体指针,应该调用它的写保持寄存器方法应该就可以设置寄存器的值了;

-------------构造函数--------------

  1. // NewNodeRegister 创建一个modbus子节点寄存器列表
  2. func NewNodeRegister(slaveID byte,
  3.         coilsAddrStart, coilsQuantity,
  4.         discreteAddrStart, discreteQuantity,
  5.         inputAddrStart, inputQuantity,
  6.         holdingAddrStart, holdingQuantity uint16) *NodeRegister {
  7.         coilsBytes := (int(coilsQuantity) + 7) / 8
  8.         discreteBytes := (int(discreteQuantity) + 7) / 8

  9.         b := make([]byte, coilsBytes+discreteBytes)
  10.         w := make([]uint16, int(inputQuantity)+int(holdingQuantity))
  11.         return &NodeRegister{
  12.                 slaveID:           slaveID,
  13.                 coilsAddrStart:    coilsAddrStart,
  14.                 coilsQuantity:     coilsQuantity,
  15.                 coils:             b[:coilsBytes],
  16.                 discreteAddrStart: discreteAddrStart,
  17.                 discreteQuantity:  discreteQuantity,
  18.                 discrete:          b[coilsBytes:],
  19.                 inputAddrStart:    inputAddrStart,
  20.                 input:             w[:inputQuantity],
  21.                 holdingAddrStart:  holdingAddrStart,
  22.                 holding:           w[inputQuantity:],
  23.         }
  24. }
复制代码


------------------写保持寄存器方法--------------

  1. // WriteHoldings 写保持寄存器
  2. func (sf *NodeRegister) WriteHoldings(address uint16, valBuf []uint16) error {
  3.         quality := uint16(len(valBuf))
  4.         sf.rw.Lock()
  5.         if (address >= sf.holdingAddrStart) &&
  6.                 ((address + quality) <= (sf.holdingAddrStart + uint16(len(sf.holding)))) {
  7.                 start := address - sf.holdingAddrStart
  8.                 end := start + quality
  9.                 copy(sf.holding[start:end], valBuf)
  10.                 sf.rw.Unlock()
  11.                 return nil
  12.         }
  13.         sf.rw.Unlock()
  14.         return &ExceptionError{ExceptionCodeIllegalDataAddress}
  15. }
复制代码


WriteHoldings这个方法有2个参数,寄存器地址和切片类型的寄存器值;
函数内不通过copy进行了赋值拷贝
  1. copy(sf.holding[start:end], valBuf)
复制代码

你先试试看

出0入0汤圆

发表于 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)

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

出0入0汤圆

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

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

出0入0汤圆

发表于 2020-3-10 10:25:29 | 显示全部楼层
Baldwin 发表于 2020-3-10 08:47
行参类型不一样吧,你定义的是byte类型的切片,函数需要的是uint16类型的切片 ...

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

出0入0汤圆

发表于 2020-3-10 10:48:10 | 显示全部楼层
原来之前用WriteHoldingsBytes()不行是因为长度那需要是切片长度的一半,没太细看库的代码,吃亏了

出0入0汤圆

发表于 2020-3-10 20:50:29 | 显示全部楼层
本帖最后由 slzm40 于 2020-3-10 21:00 编辑
lghtjpu 发表于 2020-3-10 10:48
原来之前用WriteHoldingsBytes()不行是因为长度那需要是切片长度的一半,没太细看库的代码,吃 ...


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

出0入0汤圆

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

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

出0入0汤圆

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

client 和主站是一个概念吧... 一般统称主询的都是client. 被动等询都属server.
modbus才说主站从站.
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 18:04

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表