modbus-tk学习笔记
对于modbus ASCII 模式,使用的是高位字节在前,低位字节在后。使用LRC校验。
对于modbus rtu 模式,使用的是低位字节在前,高位字节在后。使用CRC校验。
参考博客:
MODBUS学习笔记——modbus tk modbus TCP主机实现_物联网 IoT 经验分享-CSDN博客_modbus_tk
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数据地址。
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:数据
读线圈寄存器(只有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()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?