目录
一,Modbus起源
ModbusTCP协议格式
1,报文头
2,寄存器
3,功能码
练习:
工具软件使用
三方库的使用
【1】库的安装
1.库的安装配置
2.库的使用
【2】函数接口
【3】编程流程
代码实战
一,Modbus起源
- 起源:
Modbus由Modicon公司于1979年开发,是一种工业现场总线协议标准。
Modbus通信协议具有多个变种,其中有支持串口,以太网多个版本,其中最著名的是Modbus RTU、Modbus ASCII和Modbus TCP三种
其中Modbus TCP是在施耐德收购Modicon后1997年发布的。
- 分类:
1)Modbus RTU:运行在串口上的协议,采用二进制表示形式以及紧凑的数据结构,通信效率较高,应用比较广泛
2)Modbus ASCII:运行在串口上的协议,采用ASCII码传输,利用特殊字符作为字节开始和结束的标志,传输效率较低,只有在传输数据量较少的时候才会考虑它
3)Modbus TCP:运行在以太网上的协议
- 优势:
免费、简单、容易使用
- 应用场景:
Modbus协议是现在国内工业领域应用最多的协议,不只PLC设备,各种终端设备,比如水控机、水表、电表、工业秤、各种采集设备
- Modbus TCP特点
1)采用主从问答方式进行通信
2)Modbus Tcp是应用层协议,基于传输层TCP协议实现
3)Modbus Tcp端口号默认502
ModbusTCP协议格式
ModbusTcp协议包含三部分:报文头、功能码、数据
Modbus TCP/IP协议最大数据帧长度为260字节
1,报文头
共7字节,分别是:
2,寄存器
包含四种寄存器,分别是线圈、离散量输入、保持寄存器、输入寄存器
- 离散量和线圈其实就是位寄存器(每个寄存器数据占1字节),工业上主要用于控制IO设备。
线圈寄存器,类比为开关量,每一个bit都对应一个信号的开关状态。所以一个byte就可以同时控制8路的信号。比如控制外部8路io的高低。 线圈寄存器支持读也支持写,写在功能码里面又分为写单个线圈寄存器和写多个线圈寄存器。
对应上面的功能码也就是:0x01 0x05 0x0f
离散输入寄存器,离散输入寄存器就相当于线圈寄存器的只读模式,他也是每个bit表示一个开关量,而他的开关量只能读取输入的开关信号,是不能够写的。比如我读取外部按键的按下还是松开。
所以功能码也简单就一个读的 0x02
- 输入和保持寄存器是字寄存器(每个寄存器数据占2个字节),工业上主要用于存储工业设备的值。
保持寄存器,这个寄存器的单位不再是bit而是两个byte,也就是可以存放具体的数据量的,并且是可读写的。比如我我设置时间年月日,不但可以写也可以读出来现在的时间。写也分为单个写和多个写
所以功能码有对应的三个:0x03 0x06 0x10
输入寄存器,这个和保持寄存器类似,但是也是只支持读而不能写。一个寄存器也是占据两个byte的空间。类比我我通过读取输入寄存器获取现在的AD采集值
对应的功能码也就一个 0x04
3,功能码
根据四种不同的寄存器设置了8种功能码
练习:
读传感器数据,读1个寄存器数据,写出主从数据收发协议。
主机给从机:
|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-起始地址-|-寄存器个数-|
0x0000 0000 0006 01 03 0000 0001
从机给主机:
|-事务处理标识符|-协议类型-|-字节长度-|-从机ID-|-功能码-|-数据长度-|--数据--|
0x0000 0000 0005 01 03 02 0102
练习:写出控制IO设备开关的协议数据,操作1个线圈。
主机给从机:
|----------MBAP报文头----------|-功能码-|-起始地址-|-断通标志-|
0x0000 0x0000 0x0006 0x11 0x05 0x000b 0xFF00
从机回主机:
|----------MBAP报文头----------|-功能码-|-起始地址-|-断通标志-|
0x0000 0x0000 0x0006 0x11 0x05 0x000b 0xFF00
工具软件使用
- ModbusSlave/Poll
- 软件默认安装
- 破解
点击connection->connect,输入序列号即可
3)使用
先设置后连接
后连接(连接时注意先开启slave端(相当于服务器),后起poll端(相当于客户端))
查询主机ip:win + r 然后输入cmd 然后输入ipconfig 即可查询主机ip
- 网络调试助手
- Wireshark使用
安装使用wireshark时注意把杀毒软件和防火墙关闭
捕获器选择:
windows如果连接有线网络,选择本地连接/以太网
如果连接无线网络,选择WLAN
如果只是在本机上的通信,选择NPCAP Loopback apdater
或Adapter for loopback traffic capture
过滤条件:
过滤端口:tcp.port == 502
过滤IP:ip.addr == 192.168.1.156(自己的ip地址)
三方库的使用
【1】库的安装
1.库的安装配置
1. 在linux中解压压缩包
将库压缩包复制到linux下,进行解压
tar -xvf libmodbus-3.1.7.tar.gz
- 进入源码目录,创建文件夹(存放头文件、库文件)
cd libmodbus-3.1.7
mkdir install
- 执行脚本configure,进行安装配置(指定安装目录)
./configure --prefix=$PWD/install
- 执行make和make install
make//编译
make install//安装
执行完成后会在install文件夹下生产对应的头文件、库文件件夹,install用于存放产生的头文件、库文件等
2.库的使用
要想编译方便,可以将头文件和库文件放到系统路径下
sudo cp install/include/modbus/*.h /usr/include
sudo cp install/lib/* -r /lib -d
后期编译时,可以直接gcc xx.c -lmodbus
头文件默认搜索路径:/usr/include 、/usr/local/include
库文件默认搜索路径:/lib、/usr/lib
【2】函数接口
modbus_t* modbus_new_tcp(const char *ip, int port)
功能:以TCP方式创建Modbus实例,并初始化
参数:
ip :ip地址
port:端口号
返回值:成功:Modbus实例
失败:NULL
int modbus_set_slave(modbus_t *ctx, int slave)
功能:设置从机ID
参数:
ctx :Modbus实例
slave:从机ID
返回值:成功:0
失败:-1
int modbus_connect(modbus_t *ctx)
功能:和从机(slave)建立连接
参数:
ctx:Modbus实例
返回值:成功:0
失败:-1
void modbus_free(modbus_t *ctx)
功能:释放Modbus实例
参数:ctx:Modbus实例
void modbus_close(modbus_t *ctx)
功能:关闭套接字
参数:ctx:Modbus实例
int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取线圈状态,可读取多个连续线圈的状态(对应功能码为0x01)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的状态值
int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
功能:读取输入状态,可读取多个连续输入的状态(对应功能码为0x02)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的状态值
返回值:成功:返回nb的值
int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读取保持寄存器的值,可读取多个连续保持寄存器的值(对应功能码为0x03)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
失败:-1
int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
功能:读输入寄存器的值,可读取多个连续输入寄存器的值(对应功能码为0x04)
参数:
ctx :Modbus实例
addr :寄存器起始地址
nb :寄存器个数
dest :得到的寄存器的值
返回值:成功:读到寄存器的个数
失败:-1
int modbus_write_bit(modbus_t *ctx, int addr, int status);
功能:写入单个线圈的状态(对应功能码为0x05)
参数:
ctx :Modbus实例
addr :线圈地址
status:线圈状态
返回值:成功:0
失败:-1
int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src);
功能:写入多个连续线圈的状态(对应功能码为15)
参数:
ctx :Modbus实例
addr :线圈地址
nb :线圈个数
src :多个线圈状态
返回值:成功:0
失败:-1
int modbus_write_register(modbus_t *ctx, int addr, int value);
功能: 写入单个寄存器(对应功能码为0x06)
参数:
ctx :Modbus实例
addr :寄存器地址
value :寄存器的值
返回值:成功:0
失败:-1
int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src);
功能:写入多个连续寄存器(对应功能码为16)
参数:
ctx :Modbus实例
addr :寄存器地址
nb :寄存器的个数
src :多个寄存器的值
返回值:成功:0
失败:-1
【3】编程流程
- 创建实例
modbus_new_tcp
- 设置从机ID
modbus_set_slave
- 和从机进行连接
modbus_connect
- 寄存器操作
功能码对应的函数
- 关闭套接字
modbus_close
- 释放实例
modbus_free
代码实战
任务:编程实现采集传感器数据和控制硬件设备(传感器和硬件通过slave模拟)
// 1.任务:编程实现采集传感器数据和控制硬件设备(传感器和硬件通过slave模拟)
// 传感器:2个,光线传感器、加速度传感器(x\y\z)
// 硬件设备:2个,led灯、蜂鸣器
// 要求:
// 1.多任务编程:多线程、多进程
// 2.循环1s采集一次数据,并将数据打印至终端
// 3.同时从终端输入指令控制硬件设备
// 0 1 :led灯打开
// 0 0:led灯关闭
// 1 1:蜂鸣器开
// 1 0 : 蜂鸣器关
#include <stdio.h>
#include <unistd.h>
#include <modbus.h>
#include <pthread.h>
modbus_t *ctx;
uint16_t modbout_dest[128] = {};
void *mythread(void *arg)
{
while (1)
{
modbus_read_registers(ctx, 0, 4, modbout_dest);
for (int i = 0; i < 4; i++)
{
if (i == 0)
{
printf("光线传感器:");
printf("%#x\n", modbout_dest[i]);
}
else if (i == 1)
{
printf("加速度传感器x:%#x\n", modbout_dest[i]);
}
else if (i == 2)
{
printf("加速度传感器y:%#x\n", modbout_dest[i]);
}
else if (i == 3)
{
printf("加速度传感器z:%#x\n", modbout_dest[i]);
}
putchar(10);
sleep(1);
}
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
// if (argc != 3) //从终端输入ip和端口
// {
// printf("please input %s <ip,port>\n", argv[0]);
// return -1;
// }
//1,创建实例
//modbus_t *ctx = modbus_new_tcp(argv[1], atoi(argv[2]));
modbus_t *ctx = modbus_new_tcp("192.168.196.1", 502);
//2,设置从机ID
modbus_set_slave(ctx, 1);
//3,和主机进行连接
modbus_connect(ctx);
pthread_t tid;
if (pthread_create(&tid, NULL, mythread, NULL) != 0)
{
perror("pthread_create err");
return -1;
}
pthread_detach(tid);
//从终端输入
printf("o1:led灯打开***00:led灯关闭***11:蜂鸣器开***10:蜂鸣器关");
printf("请从终端输入指令控制硬件设备");
while (1)
{
int a, b;
while (1)
{
scanf("%d", &a);
scanf("%d", &b);
if (a == 0 && b == 1)
{
printf("led灯打开");
}
else if (a == 0 && b == 0)
{
printf("led灯关闭");
}
else if (a == 1 && b == 1)
{
printf("蜂鸣器开");
}
else if (a == 1 && b == 0)
{
printf("蜂鸣器关");
}
modbus_write_bit(ctx, a, b);
}
}
//6,释放Modbus实例
modbus_free(ctx);
//7,关闭套接字
modbus_close(ctx);
return 0;
}