( 百度百科,放心跳转)
Modbus 由 Modicon 公司于 1979 年开发,是一种工业现场总线协议标准。
Modbus 通信协议具有多个变种,支持串口,以太网多个版本,其中最著名的是 Modbus RTU、
Modbus ASCII 和 Modbus TCP 三种。Modbus TCP 是在施耐德收购 Modicon 后 1997 年发布的。
1、Modbus RTU(Remote Terminal Unit)
运行在串口上的协议,采用二进制表现形式以及紧凑的数据结构,通信效率高,应用广泛。
2、Modbus ASCII
运行在串口上的协议,采用 ASCII 码进行传输,并且在每个字节的开始和结束 都有特殊字符作为标志,传输效率远远低于 Modbus RTU,只有传输数据量较小时,才会考虑。
3、Modbus TCP
运行在以太网上的协议。
免费、简单、容易使用。
Modbus 协议是现在国内工业领域应用最多的协议,不只 PLC 设备,各种终端设备,比如水控机、水表、电表、工业秤、各种采集设备,都应用此协议。
有一个节点是 master 节点,其他使用 Modbus 协议参与通信的节点是 slave 节点(可多个), 每个 slave 设备都有唯一一个地址。
Modbus TCP 协议 和 Modbus RTU 协议非常相似,只要把 RTU 协议中两个字节的校验码去掉,然后在 RTU 协议的开始加上 5 个 0 和 1 个 6,通信时通过 TCP/IP 网络协议发送出去即可。
1、见“Modbus ——> 通信”;
Modbus TCP/IP 协议 最大数据帧长度为 260 字节。报文格式如下:
线圈寄存器,类比为开关量,每一个 bit 都对应一个信号的开关状态,所以 一个 byte 就可以同时控制8 路的信号。 线圈寄存器支持读也支持写,写又分为写单个线圈寄存器和写多个线圈寄存器。
对应功能码:0x01 0x05 0x0f
离散输入寄存器,相当于线圈寄存器的只读模式,也是每个 bit 表示一个开关量,其开关量只能读取输入的开关信号,是不能写的。比如读取外部按键的按下还是松开。
对应功能码: 0x02
保持寄存器,单位不再是 bit 而是两个 byte,是可以存放具体的数据量的。比如设置时间年月日,不但可以写入也可以读出。该寄存器并可读写的,写也分为写单个保持寄存器和写多个保持寄存器。
对应功能码: 0x03 0x06 0x10
输入寄存器,和保持寄存器类似,但也只支持读而不能写。一个寄存器也是占据两个 byte 的空间。比如,通过读取输入寄存器获取现在的 AD 采集值。
对应功能码: 0x04
主机 ——>从机:
报文头 + 功能码 + 起始地址 + 数量
7 + 1 + 2 + 2 = 12
从机 ——>主机:
报文头 + 功能码 + 字节计数 + 数据
7 + 1 + 1 + n = 9 + n
主机 ——>从机:
报文头 + 功能码 + 地址 + 断通标志 / 数据
7 + 1 + 2 + 2 = 12
从机 ——>主机:
原文返回
主机 ——>从机:
报文头 + 功能码 + 起始地址 + 数量 + 字节计数 + 数据
7 + 1 + 2 + 2 + 1 + n = 13 + n
从机 ——>主机:
报文头 + 功能码 + 起始地址 + 数量
7 + 1 + 2 + 2 = 12
点击 connection -> connect,输入序列号即可。
点击 connection -> connect,输入序列号即可。
如果连接有线网络,选择本地连接 / 以太网;
如果连接无线网络,选择 WLAN;
如果只是在本机上的通信,可以选择 NPCAP Loopback apdater
或 Adapter for loopback traffic capture。
1、过滤端口:tcp.port == 502
2、过滤IP:ip.addr == Windows 的IP
2、写出控制 IO 设备开关的协议数据,操作1个线圈,置1。
3、在虚拟机编写客户端,实现 poll 端功能,和 Slave 通信,读保持寄存器的三个值。
uint8_t hldreg[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03,
0x00, 0x00, 0x00, 0x03};
send(sockfd, hldreg, sizeof(hldreg), 0);
uint8_t buf[32] = {};
recv(sockfd, buf, sizeof(buf), 0);
for (int i = 0; i < buf[8]; i++)
printf("%#x ", buf[9+i]);
putchar(10);
4、编写客户端程序,实现对 Slave 单个线圈的控制(置一)。
uint8_t coil[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x05,
0x00, 0x00, 0xff, 0x00};
send(sockfd, coil, sizeof(coil), 0);
5、封装函数:设置单元标识符(从机地址)
void set_slave_id(uint8_t *p, int slave_id){
p[6] = slave_id;
}
6、封装函数:读保持寄存器
void read_hldreg(int addr, int num, uint8_t *hldreg, uint8_t *dest){
hldreg[5] = 0x06;
hldreg[7] = 0x03;
hldreg[8] = addr >> 8;
hldreg[9] = addr & 0xff;
hldreg[10] = num >> 8;
hldreg[11] = num & 0xff;
send(sockfd, hldreg, 12, 0); // 指针类型,不能 sizeof(hldreg)
recv(sockfd, dest, 64, 0); // 64 为数组 dest 的长度,sockfd 为全局变量
}