项目开发中遇到了一些需要与工业设备和PLC通信的需求,简单总结一下用到的工业协议及其在Python里的集成。
Modbus协议
Modbus 是OSI模型第 7 层上的应用层报文传输协议,它在连接至不同类型总线或网络的设备之间提供客户机/服务器通信。
物理接口
Modbus协议用到的物理接口有RS232,RS422,RS485,以太网口四种。
其中RS232,RS422,RS485使用的接口都是DP9针的接口,但是每根引脚在不同物理接口的作用不同,接入设备时注意分辨。
DP9针线引脚图
下表是三种接口线缆的引脚作用
PS:RS232母头接口和公头的区别就是2和3引脚交换
下表是三种通信接口区别
以太网口使用常规的网线即可通信
协议类型
Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。
Modbus RTU(串口): 二进制表示数据,采用循环冗余校验的校验和
[地址码] [功能码1字节] [数据] [CRC校验码2字节]
Modbus ASCII(串口): 采用人类可读的、冗长的表示输入,采用纵向冗余校验的校验和
[起始冒号] [地址码] [功能码1字节] [数据] [LRC校验码] [回车换行]
Modbus TCP(网口): 基于TCP通信,与Modbus RTU相似,取消循环冗余校验的校验和
[事务处理标识] [协议标识] [长度] [单元标识符] [功能码1字节] [数据]
Modbus UDP(网口): 基于UDP通信,与Modbus RTU相似,取消循环冗余校验的校验和
[事务处理标识] [协议标识] [长度] [单元标识符] [功能码1字节] [数据]
Modbus 协议定义了一个与基础通信层无关的简单协议数据单元(PDU)。
PDU = [功能码] [数据] ,功能码为1字节,数据长度不定,由具体功能决定。
寄存器类型
Modbus寄存器根据存放的数据类型以及各自读写特性,将寄存器分为4个部分,这4个 部分可以连续也可以不连续,由开发者决定。
功能码
Python通信
Python需要安装pymodbus模块来实现modbus通信:
pip install pymodbus
Modbus_TCP
import modbus_tk.modbus_tcp as mt # 核心组件
import modbus_tk.defines as cst # 通信参数
import logging
class ModebusTcp:
def __init__(self, host="127.0.0.1", port=502, timeout=1):
self.host = host
self.port = port
self.timeout = timeout
self.master = mt.TcpMaster(host=self.host, port=self.port, timeout_in_sec)
'''
参数含义
slave: Modbus从站地址,1到247,0为广播所有从站
function_code: 功能码
starting_address: 寄存器起始地址
quality_of_x: 寄存器读写数量,写可为0,读至少为1
output_value: 输出值,读操作无效,写操作是整数值或者一个列表
data_format:数据格式,'>'为大端模式,'<'为小端模式
'''
def _write(self, slave, function_code, address, value, data_format):
try:
write_value = self.master.execute(slave=slave, function=function_code, starting_address=address, output_value=[value], data_fotmat='>L')
logging.debug("Write success!")
except Exception as e:
logging.error("Write failed,info:{0}".format(e))
def _read(self, slave, function_code, address, data_format)
try:
read_value = self.master.execute(slave=slave, function=function_code, starting_address=address, quantity_of_x=2, data_format='>L')
logging.debug("Read success!")
except Exception as e:
logging.error("Read failed,info:{0}".format(e))
通信参数格式
slave: 从站ID,服务端设置
function_code: 根据功能设置,可以从modbus_tk.defines里查询
starting_address: 根据想要通信的数据地址设置
quality_of_x: 读/写的寄存器数量,Modbus 以16位为一个字进行编址,寄存器是16位的,可以存放两个字节 ,即1寄存器 = 2字节
output_value: (仅写)输出数据,一个int值或者一个列表
data_format: 数据格式
Mosbus_RTU
from modbus_tk import modbus_rtu
import modbus_tk.defines as cst
import logging
class ModbusRtu:
def __init__(self, port, baudrate, stopbits=1, parity="N", bytesize=8, timeout=1, xonxoff=0):
self.port = port
self.baudrate = baudrate
self.client = modbus_rtu.RtuMaster(serial.Serial(port=port, baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits))
def _write(self, slave, function_code, address, value, data_format):
try:
write_value = self.master.execute(slave=slave, function=function_code, starting_address=address, output_value=[value], data_fotmat='>L')
logging.debug("Write success!")
except Exception as e:
logging.error("Write failed,info:{0}".format(e))
def _read(self, slave, function_code, address, data_format)
try:
read_value = self.master.execute(slave=slave, function=function_code, starting_address=address, quantity_of_x=2, data_format='>L')
logging.debug("Read success!")
except Exception as e:
logging.error("Read failed,info:{0}".format(e))
参数配置除波特率和端口外与Modebus—tcp参数含义相同。
ETHERNET协议
ETHERNET同样也是网络协议,与Modbus_Tcp又很多相同之处。
ETHERNET和MODBUS TCP都是工业通讯协议,但它们的应用场景和特点不同:
ETHERNET。在TCP/IP层之上增加了用于实时数据交换和运行实时应用的CIP协议(Common Industrial Protocol)。在物理层和数据链路层使用以太网技术,在网络层和传输层使用IP协议和TCP、UDP协议来传输数据。
Modbus_Tcp。是一种中立厂商的用于管理和控制自动化设备的MODBUS系列通讯协议的派生产品。它覆盖了使用TCP/IP协议的 "Intranet"和"Internet"环境中MODBUS报文的用途。
PS:ETHERNET的默认端口是23,Modbus_Tcp的默认端口是502。
在Python中可以通过套接字来实现通信。
import socket
import logging
class Ethernet:
def __init__(self, host, port=23):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def send_data(self, data)
try:
self.socket.connect()
data = data.encode()
self.socket.send(data)
self.socket.close()
logging.debug("Send data success!")
except Exception as e:
logging.error("Send data failed!")
def get_data(self, bufsize)
try:
self.socket.connect()
re = self.socket.recv(bufsize)
self.socket.close()
logging.info("Get data:{0}".format(re))
except:
logging.error("Get data failed!")
Prifibus暂未用到,以后补充。