目录
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 RTU的特点:
Modbus ASCII的特点:
Modebus TCP的特点:
运行在以太网上的协议
- 采用主从问答方式进行通信
- Modbus Tcp是应用层协议,基于传输层Tcp协议实现
- Modbus Tcp端口号默认是502
ModbusTcp协议包含三部分:报文头,功能码,数据
组成事务处理符:2字节
可以理解为报文的序列号,一般每次通信之后就要加1以区别不同的通信数据报文
协议标识符:2字节
00 00表示Modbus TCP协议(一般不改变)
长度:2字节
表示接下来的字节长度,单位字节
单元标识符:1字节
串行链路或其他,总线上连接的远程从站地址
先了解寄存器
包含四种寄存器
功能码详表:
数据格式可以看下面分享
实例分享 | ModbusTCP报文详解
字寄存器:
位寄存器:
从机给主机回复:0x0000 0000 0005 01 03 02 0012
解析:
0x0000事务处理标识符
0000 协议类型
0006 字节长度
01 从机ID(单元标识符)
03 功能码
02 字节计数
0012 数据长度
主机给从机:
0x0000 0x0000 0x0006 0x01 0x05 0x000b 0xFF00
从机给主机:
0x0000 0x0000 0x0006 0x01 0x05 0x000b 0xFF00
模拟的是实际的控制设备,相当于服务器端,用于响应主机的请求。
模拟的是主机,相当于客户端
》1.协议分析
网络助手需要用到下面的协议
00 00 00 00 00 06 01 06 00 01 00 01
这串协议的意思是向主机号为01的0001地址写入0001值
我们可以使用网络助手进行验证
》2.slave(从机相当于服务器)设置
》3.网络调试助手设置
》4.开始通信测试
先将双方建立通信
输入ip.addr == 192.168.8.140后,按下enter键
然后用网络调试助手给slave发送数据,wireshark就抓到数据了
对抓到的数据进行基础分析
注:wireshark在嵌入式领域中并不常用,上面操作只是单纯为了抓包而抓包
//练习1
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#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;
}