目录
Modbus简介
ModbusTCP协议格式
》1.报文头(共7字节)
》2.功能码
》3.数据
练习:读传感器数据,读1个寄存器数据,写出主从数据收发协议。
练习:写出控制IO设备开关的协议数据,操作1个线圈。
模拟器使用
windows的地址获取
1. 按下ctrl+r
2. 输入cmd运行
3. 输入ipconfig获取ip
4. 查看ip
ModbusSlave(从机)界面介绍
ModbusPoll(主机)界面介绍
salve和poll使用例
网络调试助手界面介绍
网路助手的测试
Wireshark的基础使用教程
1.在虚拟机写程序实现poll端功能,编写客户端实现和Slave通信
2. 编写客户端程序实现对Slave单个线圈的控制
Modbus简介
Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是
Modbus RTU的特点:
- 运行在串口上的协议
- 采用二进制表示形式
- 紧凑的数据结构
- 通信效率高,应用广
Modbus ASCII的特点:
- 运行在串口上的协议
- 采用ASCII码传输
- 利用特殊字符作为字节开始和结束标志
- 传输数据低,只用传输数据量少的时候才考虑使用
Modebus TCP的特点:
运行在以太网上的协议
- 采用主从问答方式进行通信
- Modbus Tcp是应用层协议,基于传输层Tcp协议实现
- Modbus Tcp端口号默认是502
ModbusTCP协议格式
ModbusTcp协议包含三部分:报文头,功能码,数据
》1.报文头(共7字节)
组成事务处理符:2字节
可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文
协议标识符:2字节
00 00表示Modbus TCP协议(一般不改变)
长度:2字节
表示接下来的字节长度,单位字节
单元标识符:1字节
串行链路或其他,总线上连接的远程从站地址
》2.功能码
先了解寄存器
包含四种寄存器
- 线圈
-
- 位寄存器(每个寄存器数据占1字节)
- 主要用于控制IO设备
- 线圈寄存器,类比为开关量
- 一个byte就可以同时控制8路的信号
- 可读可写,写分为写单个写多个
- 共三种状态:读,写单个,写多个
- 离散量输入
-
- 位寄存器(每个寄存器数据占1字节)
- 主要用于控制IO设备
- 线圈寄存器的只读模式
- 只有一种状态:只能读
- 保持寄存器
-
- 字寄存器(每个寄存器数据占2个字节)
- 存储工业设备的值
- 可读可写,写分为写单个写多个
- 共三种状态:读,写单个,写多个
- 输入寄存器
-
- 字寄存器(每个寄存器数据占2个字节)
- 存储工业设备的值
- 只有一种状态:只能读
功能码详表:
》3.数据
数据格式可以看下面分享
实例分享 | ModbusTCP报文详解
字寄存器:
位寄存器:
练习:读传感器数据,读1个寄存器数据,写出主从数据收发协议。
从机给主机回复:0x0000 0000 0005 01 03 02 0012
解析:
0x0000事务处理标识符
0000 协议类型
0006 字节长度
01 从机ID(单元标识符)
03 功能码
02 字节计数
0012 数据长度
练习:写出控制IO设备开关的协议数据,操作1个线圈。
主机给从机:
0x0000 0x0000 0x0006 0x01 0x05 0x000b 0xFF00
从机给主机:
0x0000 0x0000 0x0006 0x01 0x05 0x000b 0xFF00
模拟器使用
windows的地址获取
1. 按下ctrl+r
2. 输入cmd运行
3. 输入ipconfig获取ip
4. 查看ip
ModbusSlave(从机)界面介绍
模拟的是实际的控制设备,相当于服务器端,用于响应主机的请求。
ModbusPoll(主机)界面介绍
模拟的是主机,相当于客户端
salve和poll使用例
网络调试助手界面介绍
网路助手的测试
》1.协议分析
网络助手需要用到下面的协议
00 00 00 00 00 06 01 06 00 01 00 01
这串协议的意思是向主机号为01的0001地址写入0001值
我们可以使用网络助手进行验证
》2.slave(从机相当于服务器)设置
》3.网络调试助手设置
》4.开始通信测试
Wireshark的基础使用教程
先将双方建立通信
输入ip.addr == 192.168.8.140后,按下enter键
然后用网络调试助手给slave发送数据,wireshark就抓到数据了
对抓到的数据进行基础分析
注:wireshark在嵌入式领域中并不常用,上面操作只是单纯为了抓包而抓包
1.在虚拟机写程序实现poll端功能,编写客户端实现和Slave通信
//练习1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MY_PORT 502
#define MY_ADDRESS "192.168.8.140" //根据自己情况输入
enum num{
Length =0x06,//长度
Unitid =0x01,//单元标识
Fucode =0x03,//功能码
Flowad =0x00,//起始寄存器高位
Fhigad =0x00,//起始寄存器低位
NUMlow =0x00,//数量高位
NUMhig =0x01//数量低位
};
int main(int argc, char const *argv[])
{
uint8_t buf[12]={0x00};
//1.创建套接子
int sockid;
sockid = socket(AF_INET, SOCK_STREAM, 0);
if (sockid < 0)
{
perror("socket err.");
return -1;
}
//2.填充结构提
struct sockaddr_in caddr;
memset(&caddr,0,sizeof(caddr));
caddr.sin_family = AF_INET;
caddr.sin_port = ntohs(MY_PORT);
caddr.sin_addr.s_addr = inet_addr(MY_ADDRESS);
//3.connect链接服务器
int conid = connect(sockid, (struct sockaddr *)&caddr, sizeof(caddr));
if (conid < 0)
{
perror("connect err");
return -1;
}
//4.发送消息
buf[5]=Length;//长度
buf[6]=Unitid;//单元标识
buf[7]=Fucode;//功能码
buf[8]=Flowad;//起始寄存器高位
buf[9]=Fhigad;//起始寄存器低位
buf[10]=NUMlow;//数量高位
buf[11]=NUMhig;//数量低位
int sendid = send(sockid, buf, sizeof(buf), 0);
if (sendid < 0)
{
perror("send err.");
return -1;
}
int recid = recv(sockid, buf, sizeof(buf), 0);
if (recid < 0)
{
perror("recv err.");
return -1;
}
for (int i = 0; i < sizeof(buf)-1; i++)
{
printf("%02x ", buf[i]);
}
close(sockid);
return 0;
}
开启slave端链接
读到值为0x14,对用十进制是20
2. 编写客户端程序实现对Slave单个线圈的控制
//练习2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define MY_PORT 502
#define MY_ADDRESS "192.168.8.140"
struct Coil{
uint8_t Length;//长度
uint8_t Unitid;//单元标识
uint8_t Fucode;//功能码
uint8_t CoilHeight;//线圈地址(高位)
uint8_t CoilLow;//线圈地址(低位)
uint8_t DisMkHeight;//断通标志
uint8_t DisMkLow;//断通标志
};
struct Coil num={0x06,0x01,0x05,0x00,0x00,0xff,0x00};
int main(){
uint8_t buf[12]={0x00};
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if (sockfd<0)
{
perror("socket err.");
return -1;
}
struct sockaddr_in caddr;
memset(&caddr,0,sizeof(caddr));
caddr.sin_family=AF_INET;
caddr.sin_port=ntohs(MY_PORT);
caddr.sin_addr.s_addr=inet_addr(MY_ADDRESS);
int conid=connect(sockfd,(struct sockaddr*)&caddr,sizeof(caddr));
if (conid<0)
{
perror("connect err.");
return -1;
}
//4.发送消息
buf[5]=num.Length;//长度
buf[6]=num.Unitid;//单元标识
buf[7]=num.Fucode;//功能码
buf[8]=num.CoilHeight;//线圈地址(高位)
buf[9]=num.CoilLow;//线圈地址(低位)
buf[10]=num.DisMkHeight;//断通标志
buf[11]=num.DisMkLow;//断通标志
int sendid = send(sockfd, buf, sizeof(buf), 0);
if (sendid < 0)
{
perror("send err.");
return -1;
}
int recid = recv(sockfd, buf, sizeof(buf), 0);
if (recid < 0)
{
perror("recv err.");
return -1;
}
for (int i = 0; i < sizeof(buf); i++)
{
printf("%02x ", buf[i]);
}
printf("\n");
close(sockfd);
return 0;
}