MODBUS协议从理解到应用快速解析(附带C语言简单实现)_modbusc语言

modbus协议分为很多种类,比如modbus-rtu,modbus-tcp,modbus-ascii,modbus-rtu over tcp等等,最常用的就是modbus-rtu和modbus-tcp两种类型了,其中modbus-rtu在OSI网络模型的物理层使用的大多是RS485,modbus-tcp使用的是RJ45也就是我们常说的水晶头。

为什么modbus协议搞这么麻烦,其实很简单,继续往下看。

二、modbus-rtu

这是modbus协议最开始的形态,其他版本都是基于这个版本的,理解了这个版本,其他版本就都差不多了。

1.modbus-rtu报文结构

modbus最终还是用来通信的协议,tcp通讯收发数据包,modbus收发报文,那么理解modbus报文格式将会是使用modbus协议的一项基本功。

modbus-rtu的报文分为以下几个部分:

从机地址        功能码        数据        CRC校验

8bit                 8bit           Nbit            16bit

2.modbus-rtu报文格式解析

现在你已经了解modbus的报文大概是什么结构,我们来看看具体怎么用。

**从机地址:**由于modbus协议采取一主多从的模式,故只有一个主机能主动发送报文,其他的从机只能回复收到的报文,所以在地址这里都是从机地址,主机不需要说明自己是谁。所有从机都能收到报文,但只有地址设置为报文中地址的从机会处理这条报文。由于这段数据长度规定为一字节,所以一条modbus上理论最多可以有0-255共256个从机。

**功能码:**这个字节用来告诉从机你准备让它干什么,常用的有1,2,3,4,5,6,15,16等,看到这么多数字可能会有点慌,但其实可以分为两个部分,很简单。其中01是用来读输出离散量或是1个bit的,02是用来读取输入离散量或是1个bit的,03是用来读取保持寄存器的,04用来读取输入寄存器,05用来写单个离散量,15用来写多个离散量,06用来写单个寄存器,16用来写多个寄存器。其中一个离散量就是1bit,一个寄存器大小是16bit。

**数据:**这里是最终你需要读或写的数据,不定长,最大250字节左右。

**CRC校验:**用来校验收到的数据是否受到损坏,如果损坏会回复错误,具体内容后面会讲,想详细了解校验方法自行百度。

上面说的这四部分就是modbus-rtu报文大致的结构,在实际使用中根据不同的功能只有较小的差距,下面举几个例子并说明。

读:
01,02,03,04发送的报文格式:

从站地址        功能码        地址高位        地址低位        数量高位        数量低位        CRC

8bit              8bit               8bit                8bit                8bit                 8bit              16bit

01,02,03,04回复的报文格式:

从站地址        功能码        字节数        字节1        字节n        CRC

8bit              8bit           8bit            8bit            8bit           8bit

这里发现为什么我说简单了吧,但是注意,01,02和03,04回复的解释方式是不一样的!

01,02功能码回复的字节是1bit对应一个离散量,而03,04回复的每两个字节才对应一个16位的寄存器。

现在你已经学会写所有的数据了,快来试试吧!

向地址255设备ID为2的输入寄存器写入666,用二进制或十六进制表示。

收到回复01 03 00 01 02 00 66 (十六进制,CRC校验省略),代表什么意思?

学会的朋友们可以把答案留在评论区。

写:
05,06发送的报文格式:

从站地址        功能码        地址高位        地址低位        写入高位        写入低位        CRC

8bit              8bit               8bit                8bit                8bit                 8bit              16bit

这里又有一些区别,需要用一下脑子来记一记了,用05写离散量,写入数据得发送FF 00(十六进制)才代表1(true),00 00为0。而06写寄存器,高低位加起来默认是个int16。

05,06回复的报文格式:

从站地址        功能码        地址高位        地址低位        写入高位        写入低位        CRC

8bit              8bit               8bit                8bit                8bit                 8bit              16bit

回复就简单了,正常回复就是直接复读发送的报文,哈哈。

15,16发送的报文格式:

从站地址  功能码  地址高位 地址低位  数量高位  数量低位  写入字节数  写入数据   CRC

8bit          8bit        8bit          8bit          8bit           8bit            8bit             Nbit       16bit

这里15和16写入数据的区别跟01,03一样,一个是1bit代表一个离散量,一个是16bit一个寄存器。

15,16回复的报文格式:

从站地址        功能码        地址高位       地址低位        数量高位        数量低位        CRC

8bit               8bit              8bit               8bit                  8bit                8bit            16bit

基本上也是复读,不过只复读到写入字节数之前。

异常回复:

当发送了错误的报文之后就会回复异常回复,异常回复格式为:

从站地址        异常功能码        错误码        CRC

8bit                 8bit                   8bit            16bit

异常功能码就是正常码加128,比如03请求出错,回复的异常功能码就是83H,错误码表如下:

异常码名称描述
01 (0x01)非法功能码从站设备不支持此功能码。
02 (0x02)非法数据地址指定的数据地址在从站设备中不存在。
03 (0x03)非法数据值指定的数据超过范围或者不允许使用。
04 (0x04)从站设备故障从站设备处理响应的过程中,出现未知错误等。
05 (0x05)确认从站设备已经接受请求,并且正在处理这个请求,但是需要长持续时间进行这些操作,返回这个响应防止在客户机(或主站)中发生超时错误,客户机(或主机)可以继续发送轮询程序完成报文来确认是否完成处理。
06 (0x06)从站设备忙从站设备正在处理长持续时间的程序命令。
07 (0x07)否定确认从站设备无法执行主站设备发送的请求。
08 (0x08)存储奇偶性差错指示扩展文件区不能通过一致性校验。
10 (0x0A)不可用的网关路径与网关一起使用,指示网关不能为处理请求分配输入端口值输出端口的内部通信路径。通常意味着网关是错误配置的或过载的。
11 (0x0B)网关目标设备响应失败与网关一起使用,指示没有从目标设备中获得响应,通常意味着设备未在网络中。
小结:

到这里modbus的大部分东西就讲完了,但是还有很多细节没有写,这些在使用的过程中慢慢都会了解到,下面开始讲解modbus-tcp。

三、modbus-tcp

modbus-tcp和modbus-rtu很像,所以我只说有差别的部分,由于modbus-tcp的报文是嵌在tcp数据帧的数据段中,所以就不需要校验了,tcp协议做了这个工作。并且tcp已经用ip和端口号来确认从机地址了,所以就也不需要从机地址了。

1.modbus-tcp报文结构

事务处理标识符        协议标识符        长度        单元标识符        功能码        数据

16bit                       16bit            16bit             8bit                  8bit           Nbit

后面的基本一样就是丢掉了校验,数据代表功能码后面所有其他数据,都是一样的,着重讲一下前面四个。

**事务标识符:**用于事务处理配对。回复报文会复读这一段数据。因为在以太网传输中存在一个问题,就是先发后至,我们可以利用这个事务处理标识符做一个id,来防止这种情况所造成的数据收发错乱(没人用,都是0000)。

**协议标识符:**modbus协议就是0000,固定的。

**长度:**后面的字节数

**单元标识符:**从站地址,基本没啥用,写错了也没事,原因前面讲过,很多设备的实现都会忽略这一段,但不排除有的设备非要你写对。

小结:

modbus-tcp和modbus-rtu的主要区别就是前面的一些数据,功能码后面的数据除了没有校验之外其他所有的都和rtu一样,故不再解释,下面给一个C语言收发报文的实现。

四、C语言实现收发modbus-tcp报文

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数嵌入式工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

img

img

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以+V:Vip1104z获取!!! (备注:嵌入式)

img

最后

资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!

助,可以+V:Vip1104z获取!!! (备注:嵌入式)**

img

最后

资料整理不易,觉得有帮助的朋友可以帮忙点赞分享支持一下小编~

你的支持,我的动力;祝各位前程似锦,offer不断,步步高升!!!

更多资料点击此处获qu!!

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MODBUS协议是一种串行通信协议,通常用于连接工业自动化设备。在C语言实现MODBUS协议需要使用串口通信库和MODBUS协议的相关函数库。下面是一个简单的示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <fcntl.h> #include <termios.h> #define MODBUS_READ_HOLDING_REGISTERS 0x03 int open_serial_port(const char *device) { int fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY); if (fd == -1) { perror("open"); exit(-1); } fcntl(fd, F_SETFL, 0); return fd; } void set_serial_port(int fd, int speed) { struct termios options; tcgetattr(fd, &options); cfsetispeed(&options, speed); cfsetospeed(&options, speed); options.c_cflag |= (CLOCAL | CREAD); options.c_cflag &= ~PARENB; options.c_cflag &= ~CSTOPB; options.c_cflag &= ~CSIZE; options.c_cflag |= CS8; options.c_oflag &= ~OPOST; options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); options.c_cc[VMIN] = 0; options.c_cc[VTIME] = 5; tcsetattr(fd, TCSANOW, &options); } int modbus_read_holding_registers(int fd, unsigned char *buffer, int address, int count) { unsigned char request[8]; request[0] = address; request[1] = MODBUS_READ_HOLDING_REGISTERS; request[2] = (count >> 8) & 0xff; request[3] = count & 0xff; request[4] = (address >> 8) & 0xff; request[5] = address & 0xff; int length = write(fd, request, 6); if (length == -1) { perror("write"); return -1; } length = read(fd, buffer, count * 2 + 5); if (length == -1) { perror("read"); return -1; } return length; } int main(int argc, char **argv) { if (argc != 2) { fprintf(stderr, "Usage: modbus /dev/ttyUSB0\n"); exit(-1); } int fd = open_serial_port(argv[1]); set_serial_port(fd, B9600); unsigned char buffer[1024]; int length = modbus_read_holding_registers(fd, buffer, 0, 10); if (length == -1) { fprintf(stderr, "Failed to read holding registers\n"); exit(-1); } for (int i = 0; i < length; i++) { printf("%02x ", buffer[i]); } printf("\n"); close(fd); return 0; } ``` 上面的示例中,我们使用了Linux系统的串口库来打开和配置串口,然后实现了一个读取Holding Registers的函数。最后,我们在main函数中调用了modbus_read_holding_registers函数来读取寄存器数据,并输出到终端。注意,这里的寄存器地址和寄存器数量是固定的,需要根据实际情况进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值