使用 Modbus 实现主从多设备互联

概述

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议事实上的业界标准,并且现在是工业电子设备之间常用的连接方式。


Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信。举个例子:一个由测量温度和湿度的装置,并且将结果发送给计算机。


在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。

接口

Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。大多数Modbus设备通信通过串口EIA-485物理层进行。

目前Python轻应用仅支持基于485接口的RTU通信模式。

工作模式

Modbus协议是一个master/slave架构的协议。有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点。 每一个slave设备都有一个唯一的地址。在串行和MB+网络中,只有被指定为主节点的节点可以启动一个命令。


一个ModBus命令包含了打算执行的设备的Modbus地址。所有设备都会收到命令,但只有指定位置的设备会执行及回应指令 (地址0例外,指定地址0的指令是广播指令,所有收到指令的设备都会运行,不过不回应指令)。 所有的Modbus命令包含了检查码,以确定到达的命令没有被破坏。


基本的ModBus命令能指令一个RTU改变它的寄存器的某个值,控制或者读取一个I/O端口,以及指挥设备回送一个或者多个其寄存器中的数据。


有许多modems和网关支持Modbus协议,因为Modbus协议很简单而且容易复制。它们当中一些为这个协议特别设计的。 有使用有线、无线通信甚至短消息和GPRS的不同实现。不过设计者需要克服一些包括高延迟和时序的问题。

案例

目前多个HaaS设备( HaaS100, HaaS506,HaaS600 )均支持485接口,下面我们以两个 HaaS100 为例来演示如何基于 Modbus 协议实现设备间通信。

Modbus互联

案例中我们以左边的板子为主机,右边的板子为从机,实现两个设备的命令发送及响应。更多的设备均可以通过 Modbus 总线连接,这里我们不做更多扩展。


Python轻应用支持的 Modbus 接口可以参考 modbus — Modbus协议

示例演示

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# -*- encoding: utf-8 -*-
import time
import usys as sys
import modbus as mb

serial_port = 1         # 485端口号,haas100使用的端口为1
serial_baudrate = 9600  # 波特率
slave_addr = 1          # 从机设备地址
reg_addr = 1            # 寄存器地址
reg_value = 2           # 寄存器数据
req_reg_cnt = 10        # 寄存器数目
resp_timeout = 200      # 响应等待时间,单位为毫秒(ms
rw_timeout = 500        # 串口读写超时时间,单位为毫秒(ms
loop_cnt = 5


def mb_master():
   mb.init(serial_port, serial_baudrate, mb.PARITY_NONE, resp_timeout)

   loop = 0
   while(loop < loop_cnt):

      res = mb.writeHoldingRegister(
            slave_addr, reg_addr, reg_value, rw_timeout)
      print('modbus master write Holding Reg result:', res)

      data = bytearray(req_reg_cnt * 2)
      res = mb.readHoldingRegisters(
            slave_addr, reg_addr, req_reg_cnt, data, -1)
      print('modbus master read Holding Regs result:', res)

      loop = loop + 1
      time.sleep(1)

   mb.deinit()


def mb_slave():
   mb.init(serial_port, serial_baudrate, mb.PARITY_NONE, 100)

   while True:
      data = mb.recv()

      status  = data[0]
      rx      = data[1]

      # 收到有效数据,处理并响应请求
      if(status == 0):
            # 提取数据并判断是否为发送给自己
            if(rx[0] == slave_addr):

               if rx[1] == 0x06:
                  resp = bytearray(5)
                  resp[0] = rx[1]
                  resp[1] = rx[2]
                  resp[2] = rx[3]
                  resp[3] = rx[4]
                  resp[4] = rx[5]

               elif rx[1] == 0x03:
                  # 填充假数据作为响应数据
                  resp = bytearray(req_reg_cnt * 2 + 2)
                  resp[0] = rx[1]
                  resp[1] = req_reg_cnt * 2
                  for i in range(req_reg_cnt):
                        resp[i*2+2] = 0
                        resp[i*2+3] = i
               else:
                  print('unsupported cmd for demo')

               print('resp data:', resp)
               mb.send(slave_addr, resp, 100)

   mb.deinit()


if __name__ == '__main__':

   if(len(sys.argv) == 2):
      config = sys.argv[1]
      if(config == 'master'):
            mb_master()
      elif(config == 'slave'):
            mb_slave()
   else:
      print('Should specifiy master or slave')

在主机设备上运行命令:

python /data/pyamp/main.py master

在从机设备上运行命令:

python /data/pyamp/main.py slave

最终可以从主机从机上得到不同的输出:

主机输出:

(ash:/data)# python /data/pyamp/main.py master
Welcome to MicroPython
modbus master write Holding Reg result: (0, 1, 2, 0)
modbus master read Holding Regs result: (0, 20)
modbus master write Holding Reg result: (0, 1, 2, 0)
modbus master read Holding Regs result: (0, 20)
modbus master write Holding Reg result: (0, 1, 2, 0)
modbus master read Holding Regs result: (0, 20)
modbus master write Holding Reg result: (0, 1, 2, 0)
modbus master read Holding Regs result: (0, 20)
modbus master write Holding Reg result: (0, 1, 2, 0)
modbus master read Holding Regs result: (0, 20)

从机输出:

(ash:/data)# python /data/pyamp/main.py slave
Welcome to MicroPython
[ 108.558]<E>modbusm frame is too short or CRC error

[ 108.558]<E>modbus Failed to disassemble frame
resp data: bytearray(b'\x06\x00\x01\x00\x02')
resp data: bytearray(b'\x03\x14\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\t')
resp data: bytearray(b'\x06\x00\x01\x00\x02')
resp data: bytearray(b'\x03\x14\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\t')
resp data: bytearray(b'\x06\x00\x01\x00\x02')
resp data: bytearray(b'\x03\x14\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\t')
resp data: bytearray(b'\x06\x00\x01\x00\x02')
resp data: bytearray(b'\x03\x14\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\t')
resp data: bytearray(b'\x06\x00\x01\x00\x02')
resp data: bytearray(b'\x03\x14\x00\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x00\x06\x00\x07\x00\x08\x00\t')

可以看到从机正确地响应了主机的请求并给予回答,主机的返回状态码 为0,主从机通信正常。