IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> 用Python实现Modbus-RTU协议及串口调试 -> 正文阅读

[Python知识库]用Python实现Modbus-RTU协议及串口调试

用Python实现Modbus-RTU协议及串口调试

最近由于要测试几块客户使用的现场仪表的通信(Modbus-RTU协议),就用Python写了个Modbus-RTU协议的串口调试模块,主要涉及了bytes类型字节串的使用,串口模块pyserial的使用,循环冗余校验CRC计算模块crcmod的使用,以及struct内置模块的使用。如果没有安装以上模块请按下面命令安装。

pip install pyserial
pip install crcmod

实现CRC16校验

首先按照Modbus-RTU协议的规范,所有通信帧末尾都要有2个字节的CRC16字节的校验码,以保证通信的可靠性。所以首先要实现CRC16校验算法,我们可以直接使用crcmod模块的已有算法,代码如下:

import crcmod
# CRC16校验,返回整型数
def crc16(veritydata):
    if not veritydata:
        return
    crc16 = crcmod.mkCrcFun(0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000)
    return crc16(veritydata)

其中函数参数veritydata是一个字节串bytes变量,表示要进行校验的数据。
当然你也可以自己按Modbus-RTU协议给出的c语言算法自己用Python写一个。
由于客户仪表主要使用了Modbus_RTU协议的03和04功能号,通过03和04功能号主机读取仪表从机的实时数据,用Python主要实现这两个功能号的协议,03和04功能号协议具体可以参考Modbus-RTU标准文档。

用函数实现Modbus-RTU的03和04功能主机->从机的命令帧

代码如下:

def mmodbus03or04(add, startregadd, regnum, funcode=3):
    if add < 0 or add > 0xFF or startregadd < 0 or startregadd > 0xFFFF or regnum < 1 or regnum > 0x7D:
        print("Error: parameter error")
        return
    if funcode != 3 and funcode != 4:
        print("Error: parameter error")
        return
    sendbytes = add.to_bytes(1, byteorder="big", signed=False)
    sendbytes = sendbytes + funcode.to_bytes(1, byteorder="big", signed=False) + startregadd.to_bytes(2, byteorder="big", signed=False) + \
                regnum.to_bytes(2, byteorder="big", signed=False)
    crcres = crc16(sendbytes)
    crc16bytes = crcres.to_bytes(2, byteorder="little", signed=False)
    sendbytes = sendbytes + crc16bytes
    return sendbytes

此函数一共有4个参数,含义如下。
add:Modbus从站地址(仪表地址)
startregadd:要读取的开始寄存器地址(从0开始的绝对地址)
regnum:要读取的寄存器个数
funcode:功能号,默认值是3
此函数首先判断了从站地址是否超限,开始寄存器地址是否超限,寄存器个数是否超限,功能号是否正确等。
然后将几个参数以及CRC16的校验码形成一个可以发送串口的字节串后返回。注意int整形的to_bytes成员函数,此函数用于将整形数转换为字节码,第一个参数是转换字节码的字节个数,第二个参数是转换是按大端模式(big)还是小端模式(little)。第三个参数标识整形数按有符号整形转换还是无符号整形转换。

将Modbus从机返回的数据帧的解析为数值的函数

代码如下:

def smodbus03or04(recvdata, valueformat=0, intsigned=False):
    if not recvdata:
        print("Error: data error")
        return
    if not checkcrc(recvdata):
        print("Error: crc error")
        return
    datalist = list(recvdata)
    if datalist[1] != 0x3 and datalist[1] != 0x4:
        print("Error: recv data funcode error")
        return
    bytenums = datalist[2]
    if bytenums % 2 != 0:
        print("Error: recv data reg data error")
        return
    retdata = []
    if valueformat == 0:
        floatnums = bytenums / 4
        print("float nums: ", str(floatnums))
        floatlist = [0, 0, 0, 0]
        for i in range(int(floatnums)):
            floatlist[1] = datalist[3+i*4]
            floatlist[0] = datalist[4+i*4]
            floatlist[3] = datalist[5+i*4]
            floatlist[2] = datalist[6+i*4]
            bfloatdata = bytes(floatlist)
            [fvalue] = struct.unpack('f', bfloatdata)
            retdata.append(fvalue)
            print(f'Data{i+1}: {fvalue:.3f}')
    elif valueformat == 1:
        shortintnums = bytenums / 2
        print("short int nums: ", str(shortintnums))
        for i in range(int(shortintnums)):
            btemp = recvdata[3+i*2:5+i*2]
            shortvalue = int.from_bytes(btemp, byteorder="big", signed=intsigned)
            retdata.append(shortvalue)
            print(f"Data{i+1}: {shortvalue}")
    return retdata

此函数的3个参数含义如下:
recvdata:Modbus-RTU从站在接收03和04功能号命令帧后的发回主站的数据帧,bytes字节串类型。
valueformat:寄存器中值的格式,0代表用2个寄存器4个字节表示一个单精度浮点数,1代表1个寄存器(2字节)存放1个16位整形值,默认为0,(仪表用的是单精度浮点数)
intsigned:当寄存器数据值格式是整形时,true则按照有符号整形转换,false则按无符号整形转换。
首先是对接收的数据帧的各种错误判断,CRC16校验值核对等判断。如果数据值格式是单精度浮点数则按照仪表规定的浮点数格式进行转换,先将接收的字节串转换为整形列表,然后根据浮点数个数进行迭代,将接收的数据转换为浮点数,并放入列表中返回,要注意的是如何将bytes类型转换为一个浮点数,这里使用了struct模块的unpack函数。将浮点数转换为bytes类型则使用struct模块的pack函数。具体使用方法请参考说明文档。当数据值格式是16位整形时,则将切边出来每个值对应的bytes字节串转换为整形数,并放到列表中返回。

用串口读取仪表数据

串口读写使用pyserial模块,代码如下:

if __name__ == '__main__':
    slaveadd = 1 	# modbus从站地址
    startreg = 0 	# 开始寄存器地址
    regnums = 40 	# 寄存器个数
    send_data = mmodbus03or04(slaveadd, startreg, regnums)
    print("send data : ", send_data.hex())
    com = serial.Serial("com3", 9600, timeout=0.8)
    starttime = time.time()
    com.write(send_data)
    recv_data = com.read(regnums*2+5)
    endtime = time.time()
    if len(recv_data) > 0:
        print("recv: ", recv_data.hex())
    print(f"used time: {endtime-starttime:.3f}")
    com.close()
    smodbus03or04(recv_data)

serial模块的Serial函数常用的3个参数是:串口号、波特率、超时时间(s)。这里在发送了命令帧后,根据命令帧中的寄存器个数计算出需要读取多少个字节的串口数据。读取后用解析函数得到具体数值。

完整的代码如下:

import serial
import crcmod
import time
import struct

# CRC16校验,返回整型数
def crc16(veritydata):
    if not veritydata:
        return
    crc16 = crcmod.mkCrcFun(0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000)
    return crc16(veritydata)

# 校验数据帧的CRC码是否正确
def checkcrc(data):
    if not data:
    	return False
    if len(data) <= 2:
        return False
    nocrcdata = data[:-2]
    oldcrc16 = data[-2:]
    oldcrclist = list(oldcrc16)
    crcres = crc16(nocrcdata)
    crc16byts = crcres.to_bytes(2, byteorder="little", signed=False)
    # print("CRC16:", crc16byts.hex())
    crclist = list(crc16byts)
    if oldcrclist[0] != crclist[0] or oldcrclist[1] != crclist[1]:
        return False
    return True

# Modbus-RTU协议的03或04读取保存或输入寄存器功能主-》从命令帧
def mmodbus03or04(add, startregadd, regnum, funcode=3):
    if add < 0 or add > 0xFF or startregadd < 0 or startregadd > 0xFFFF or regnum < 1 or regnum > 0x7D:
        print("Error: parameter error")
        return
    if funcode != 3 and funcode != 4:
        print("Error: parameter error")
        return
    sendbytes = add.to_bytes(1, byteorder="big", signed=False)
    sendbytes = sendbytes + funcode.to_bytes(1, byteorder="big", signed=False) + startregadd.to_bytes(2, byteorder="big", signed=False) + \
                regnum.to_bytes(2, byteorder="big", signed=False)
    crcres = crc16(sendbytes)
    crc16bytes = crcres.to_bytes(2, byteorder="little", signed=False)
    sendbytes = sendbytes + crc16bytes
    return sendbytes

# Modbus-RTU协议的03或04读取保持或输入寄存器功能从-》主的数据帧解析(浮点数2,1,4,3格式,16位短整形(定义正负数))
def smodbus03or04(recvdata, valueformat=0, intsigned=False):
    if not recvdata:
        print("Error: data error")
        return
    if not checkcrc(recvdata):
        print("Error: crc error")
        return
    datalist = list(recvdata)
    if datalist[1] != 0x3 and datalist[1] != 0x4:
        print("Error: recv data funcode error")
        return
    bytenums = datalist[2]
    if bytenums % 2 != 0:
        print("Error: recv data reg data error")
        return
    retdata = []
    if valueformat == 0:
        floatnums = bytenums / 4
        print("float nums: ", str(floatnums))
        floatlist = [0, 0, 0, 0]
        for i in range(int(floatnums)):
            floatlist[1] = datalist[3+i*4]
            floatlist[0] = datalist[4+i*4]
            floatlist[3] = datalist[5+i*4]
            floatlist[2] = datalist[6+i*4]
            bfloatdata = bytes(floatlist)
            [fvalue] = struct.unpack('f', bfloatdata)
            retdata.append(fvalue)
            print(f'Data{i+1}: {fvalue:.3f}')
    elif valueformat == 1:
        shortintnums = bytenums / 2
        print("short int nums: ", str(shortintnums))
        for i in range(int(shortintnums)):
            btemp = recvdata[3+i*2:5+i*2]
            shortvalue = int.from_bytes(btemp, byteorder="big", signed=intsigned)
            retdata.append(shortvalue)
            print(f"Data{i+1}: {shortvalue}")
    return retdata

if __name__ == '__main__':
    slaveadd = 1
    startreg = 0
    regnums = 40
    send_data = mmodbus03or04(slaveadd, startreg, regnums)
    print("send data : ", send_data.hex())
    com = serial.Serial("com3", 9600, timeout=0.8)
    starttime = time.time()
    com.write(send_data)
    recv_data = com.read(regnums*2+5)
    endtime = time.time()
    if len(recv_data) > 0:
        print("recv: ", recv_data.hex())
    print(f"used time: {endtime-starttime:.3f}")
    com.close()
    smodbus03or04(recv_data)

码字不易,如果本文对您有用请随手点个赞,谢谢!^_^

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-09-05 10:46:11  更:2021-09-05 10:47:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 13:58:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码