modbus-tk学习笔记

对于modbus ASCII 模式,使用的是高位字节在前,低位字节在后。使用LRC校验。

对于modbus rtu 模式,使用的是低位字节在前,高位字节在后。使用CRC校验。

参考博客:

modbus 入门篇,不冗长,很好理解!

MODBUS学习笔记——modbus tk modbus TCP主机实现_物联网 IoT 经验分享-CSDN博客_modbus_tk

modbus协议中的寄存器理解

0.前言

modbus是一种古老但是高效的应用层协议。在嵌入式和PC机领域有多种方法实现modbus协议栈,modbus又分为从机和主机,从机和主机在协议栈的实现上存在不同。在不能运行linux的嵌入式系统中,freemodbus是一个完善的从机协议栈,在能够运行linux的嵌入式系统中存在多种选择,而modbus tk是使用python语言实现的modbus协议栈, 该函数库即支持主机也支持从机,即支持RTU也支持TCP。
有了modbus TK,那么在树莓派中加入一个modbus TCP实现从机功能,也就是分分钟的事情。

官方源码:git clone https://github.com/ljean/modbus-tk.git

用到的软件:

链接:https://pan.baidu.com/s/1iCfk3c_eRQlzY5cEDJa-cA
提取码:2g3l

1、名词解释

功能码:用于表示信息帧的功能

参考博客:modbus功能码定义和样例

0x01: 读线圈寄存器
0x02: 读离散输入寄存器
0x03: 读保持寄存器
0x04: 读输入寄存器
0x05: 写单个线圈寄存器
0x06: 写单个保持寄存器
0x0f: 写多个线圈寄存器
0x10: 写多个保持寄存器

  • 线圈寄存器:实际上就可以类比为开关量,每个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。对应上面的功能码也就是:0x01 0x05 0x0f

  • 离散输入寄存器:如果线圈寄存器理解了这个自然也明白了。离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。所以功能码也简单就一个读的 0x02

  • 保持寄存器:这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写,所以功能码有对应的三个:0x03 0x06 0x10

  • 输入寄存器:只剩下这最后一个了,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值。对应的功能码也就一个 0x04


报文一个报文就是一帧数据,一个数据帧就一个报文: 指的是一串完整的指令数据,就像下面的一串数据。

01 06 00 01 00 17 98 04

01 06 00 01 00 17 98 04
从机地址 功能码 数据地址 数据 CRC校验

这一串数据的意思是:把数据 0x0017(十进制23) 写入1号从机地址 0x0001数据地址。

modbus-tk学习笔记_第1张图片

ADU: 应用数据单元
PDU: 协议数据单元


CRC校验:上面的 98 04 是它前面的数据(01 06 00 01 00 17)通过一算法计算出来的。

作用:在数据传输过程中可能数据会发生错误,CRC检验检测接收的数据是否正确。比如主机发出01 06 00 01 00 17 98 04,那么从机接收到后要根据01 06 00 01 00 17 再计算CRC校验值,从机判断自己计算出来的CRC校验是否与接收的CRC校验(98 04主机计算的)相等,如果不相等那么说明数据传输有错误这些数据不能要。

2、数据传输

INT8U   OX[20];/*输出线圈*/
INT8U   IX[20];/*输入线圈*/
INT16U  HoldDataReg[30];/*保存寄存器*/
INT16U  InDataReg[30];/*输入寄存器*/

2.1、主机对从机写数据操作

如果从机接收到一个报文那么就对报文进行解析执行相应的处理

01 06 00 01 00 17 98 04
从机地址 功能码 数据待写入起始地址 数据 CRC校验

假如本机地址是 1 ,那么单片机接收到这串数据根据数据计算CRC校验判断数据是否正确,如果判断数据无误,则结果是:HoldDataReg[1] = 0x0017;
MODBUS主机就完成了一次对从机数据的写操作,实现了通讯。

2.2、主机对从机读数据操作

主机进行读HoldDataReg[1] 操作,则报文是:

主机发送给从机

01 03 00 01 00 01 D5 CA
从机地址 功能码 数据地址 读取数据个数 CRC校验

单片机接收到这串数据根据数据计算CRC校验判断数据是否正确,如果判断数据无误,则返数据给主机,返回的信息也是有格式的:

从机发送给主机

01 03 02 0017 F8 4A
从机地址 功能码 数据字节个数 两个字节数据 CRC校验

MODBUS主机就完成了一次对从机数据的读操作,实现了通讯。

3、实验

  • 主机写从机:master.execute(slave 地址,功能码,数据待写入起始地址,output_value=[待写入的数据,列表形式]):CRC不需要我们添加。
  • 主机读从机:master.execute(slave 地址,功能码,数据待读取起始地址,读取数据个数):CRC不需要我们添加。

写线圈寄存器(只有0和1):

red = master.execute(2, cst.WRITE_MULTIPLE_COILS, 0, output_value=[1,1,0,1,0])

2:Slave ID(Slave地址)

cst.WRITE_MULTIPLE_COILS:功能码

0:数据起始地址

output_value:数据

modbus-tk学习笔记_第2张图片


读线圈寄存器(只有0和1):

red = master.execute(2, cst.READ_COILS, 0, 2)

返回:(1, 1)

其他的寄存器操作类似。

4、源码

# -*- coding: utf_8 -*-


import serial
import modbus_tk
import modbus_tk.defines as cst
from modbus_tk import modbus_rtu


def mod(PORT="com1"):
    red = []
    alarm = ""
    try:
        # 设定串口为从站
        master = modbus_rtu.RtuMaster(serial.Serial(port=PORT,
                                                    baudrate=9600, bytesize=8, parity='N', stopbits=1))
        master.set_timeout(5.0)
        master.set_verbose(True)


        red = master.execute(2, cst.READ_COILS, 0, 2)  # 这里可以修改需要读取的功能码
        print(red)
        alarm = "正常"
        return list(red), alarm

    except Exception as exc:
        print(str(exc))
        alarm = (str(exc))

    return red, alarm  ##如果异常就返回[],故障信息


if __name__ == "__main__":
    mod()

你可能感兴趣的:(通讯协议,嵌入式)