一、Modbus 协议
Modbus簇主要有三种协议:Modbus-RTU、Modbus-ASCII、Modbus-TCP
工控行业通常的设备组网看,三者应用的范围如下:
image.png
三者模型区别如下:
image.png
如上图所示,串行传输的物理层是RS-485或RS-232,数据链路层是Modbus的串行传输协议;Modbus TCP传输的1、2、3、4层实现和日常所见的以太网、因特网一样,Modbus默认采用的TCP端口号是502。
三者报文格式的区别如下:
image.png
RTU相比较ASCII具备更紧凑的报文流,传输效率更高,目前MODBUS-ASCII已经应用较少。
Modbus的操作对象有4种:线圈、离散输入、保持寄存器、输入寄存器。
image.png
常用功能码如下:
image.png
二、安装移植libmodbus
libmodbus安装编译见:
https://www.cnblogs.com/bliss-/p/12376424.html
三、libmodbus API
1 .初始化
/*
以TCP的方式创建libmobus实例
char *ip:连接的IP地址
int port: 连接的IP端口
*/
modbus_t *modbus_new_tcp(const char *ip, int port);
/*
以串口的方式创建libmobus实例
onst char *device:连接的串口号,类似是这样'\\\\.\\COM10'
int baud: 波特率
char parity:奇偶校验
int data_bit:数据位
int stop_bit:停止位
*/
modbus_t *modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit);
/*
释放libmodbus实例,使用完libmodbus需要释放掉
modbus_t *ctx:libmodbus实例
*/
void modbus_free(modbus_t *ctx);
2.读取
/*
读取线圈状态,可读取多个连续线圈的状态
modbus_t *ctx:Modbus实例
int addr: 线圈地址
int nb:读取线圈的个数
uint8_t *dest: 传出的状态值
*/
int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
/*
读取输入状态,可读取多个连续输入的状态
modbus_t *ctx:Modbus实例
int addr:输入地址
int nb:读取输入的个数
uint8_t *dest:传出的状态值
*/
int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
/*
读取输入寄存器的值,可读取多个连续输入输入寄存器
modbus_t *ctx:Modbus实例
int addr:输入地址
int nb:读取输入寄存器的个数
uint8_t *dest:传出的寄存器值
*/
int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
/*
读取保持寄存器的值,可读取多个连续输入保持寄存器
modbus_t *ctx:Modbus实例
int addr:输入地址
int nb:读取保持寄存器的个数
uint8_t *dest:传出的寄存器值
*/
int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
3.写入
/*
写入单个线圈的状态
modbus_t *ctx:Modbus实例
int addr:线圈地址
int status:线圈状态
*/
int modbus_write_bit(modbus_t *ctx, int addr, int status);
/*
写入多个连续线圈的状态
modbus_t *ctx:Modbus实例
int addr:线圈地址
int nb:线圈个数
const uint8_t *src:多个线圈状态
*/
int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *src);
/*
写入单个寄存器
modbus_t *ctx:Modbus实例
int addr:寄存器地址
int value:寄存器的值
*/
int modbus_write_register(modbus_t *ctx, int addr, int value);
/*
写入多个连续寄存器
int addr:寄存器地址
int nb:寄存器的个数
const uint16_t *src:多个寄存器的值
*/
int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src);
四、主从站通信流程
image.png
五、RTU 测试程序
modbus_rtu.c
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include "modbus/modbus.h"
int main()
{
modbus_t *mb;
uint8_t tab_coil[10] = {0};
uint8_t tab_input_coil[10] = {0};
uint16_t tab_reg[10] = {0};
uint16_t tab_input_reg[10] = {0};
int i;
int coils,input_coils,regs,input_regs;
//打开端口
mb = modbus_new_rtu("/dev/ttymxc2",9600,'N',8,1);
//设置从地址 为1
modbus_set_slave(mb,1);
//建立链接
modbus_connect(mb);
int num = 0;//读取次数
while (1)
{
memset(tab_coil,0,10*1);
memset(tab_input_coil,0,10*1);
memset(tab_reg,0,10*2);
memset(tab_input_reg,0,10*2);
//读取线圈状态 //寄存器地址、数量、数据缓存
coils = modbus_read_bits(mb,100,1,tab_coil);
usleep(100);
//读取输入线圈状态 //寄存器地址、数量、数据缓存
input_coils = modbus_read_input_bits(mb,200,1,tab_input_coil);
usleep(100);
//读取保持寄存器 //寄存器地址、数量、数据缓存
regs = modbus_read_registers(mb,100,5,tab_reg);
usleep(100);
//读取输入寄存器 //寄存器地址、数量、数据缓存
input_regs = modbus_read_input_registers(mb,100,6,tab_input_reg);
//写线圈状态 1
modbus_write_bit(mb,100,1);
printf("----------------------------------------------------\n");
/*线圈数据*/
printf("[%4d][read coils num = %d]",num,coils);
for(i=0;i<1;i++)
{
printf("<%#x>",tab_coil[i]);
}
printf("\n");
/*输入线圈数据*/
printf("[%4d][read input coils num = %d]",num,input_coils);
for(i=0;i<1;i++)
{
printf("<%#x>",tab_input_coil[i]);
}
printf("\n");
/*保持寄存器数据*/
printf("[%4d][read registers num = %d]",num,regs);
for(i=0;i<5;i++)
{
printf("<%#x>",tab_reg[i]);
}
printf("\n");
/*输入寄存器数据*/
printf("[%4d][read input registers num = %d]",num,input_regs);
num++;
for(i=0;i<6;i++)
{
printf("<%#x>",tab_input_reg[i]);
}
printf("\n");
printf("----------------------------------------------------\n");
sleep(1);
//写线圈状态 0
modbus_write_bit(mb,100,0);
}
//关闭modbus端口
modbus_close(mb);
//释放modbus资源
modbus_free(mb);
return 0;
}
程序流程图
image.png
六、报文分析
1.读多个保持寄存器(功能码:03)
主站请求:01 03 00 64 00 05 c4 16
01从地址
03功能码:读多个保持寄存器
00 64寄存器起始地址:100
00 05 寄存器个数:5 (16bit,若为32bit则算2个)
c4 16 CRC校验码
从站响应:01 03 0a 00 00 00 01 04 9f 00 00 2a f8 bf 0c
01从地址
03功能码:读多个寄存器
0a数据大小:10byte
00 00
00 01
04 9f
00 00 2a f8 b
bf 0c CRC校验码
对应测试数据
image.png