使用 Modbus 实现主从多设备互联¶
概述¶
Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议事实上的业界标准,并且现在是工业电子设备之间常用的连接方式。
Modbus允许多个 (大约240个) 设备连接在同一个网络上进行通信。举个例子:一个由测量温度和湿度的装置,并且将结果发送给计算机。
在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。
接口¶
Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。大多数Modbus设备通信通过串口EIA-485物理层进行。
工作模式¶
Modbus协议是一个master/slave架构的协议。有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点。 每一个slave设备都有一个唯一的地址。在串行和MB+网络中,只有被指定为主节点的节点可以启动一个命令。
一个ModBus命令包含了打算执行的设备的Modbus地址。所有设备都会收到命令,但只有指定位置的设备会执行及回应指令 (地址0例外,指定地址0的指令是广播指令,所有收到指令的设备都会运行,不过不回应指令)。 所有的Modbus命令包含了检查码,以确定到达的命令没有被破坏。
基本的ModBus命令能指令一个RTU改变它的寄存器的某个值,控制或者读取一个I/O端口,以及指挥设备回送一个或者多个其寄存器中的数据。
有许多modems和网关支持Modbus协议,因为Modbus协议很简单而且容易复制。它们当中一些为这个协议特别设计的。 有使用有线、无线通信甚至短消息和GPRS的不同实现。不过设计者需要克服一些包括高延迟和时序的问题。
案例¶
案例中我们以左边的板子为主机,右边的板子为从机,实现两个设备的命令发送及响应。更多的设备均可以通过 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,主从机通信正常。