Modbus通讯开发随记2——基于LibModbus库的读取写入测试

系列文章目录

Modbus通讯开发随记1——LibModbus库的学习
Modbus通讯开发随记3——基于LibModbus库的简易Modbus主机类封装(C++版)


1. LibModbus安装和使用

关于LibModbus驱动库的安装推荐参考:
[LibModbus驱动库的使用]([https://blog.csdn.net/whik1194/article/details/119010616]
  1. 下载
    源代码建议下载官方仓库稳定版本:https://libmodbus.org/releases/libmodbus-3.1.6.tar.gz
  2. 解压
    解压后将生成.\libmodbus-3.1.6文件夹,其中.\libmodbus-3.1.6\src中为LibModbus库源文件和头文件;.\libmodbus-3.1.6\doc中为各个库函数的介绍文档;.\libmodbus-3.1.6\tests中为用例
  3. 库安装
    Libmodbus库在Windows下的安装和使用
  4. 将Config.h与./src目录下的*.c与*.h文件复制到项目目录(文件夹)下。

2 保存寄存器测试demo

2.1 从机demo

代码如下(示例):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "modbus.h"

int main(int argc, char *argv[]){
    int socket=-1;
    uint8_t *query;
    modbus_t *mb;
    int ret;
    _modbus_mapping_t *mb_mapping;
    
    mb=modbus_new_tcp("127.0.0.1", 1502);
    query = (uint8_t *)malloc(MODBUS_TCP_MAX_ADU_LENGTH);
    if((mb_mapping=modbus_mapping_new_start_address(0,0,0,0,15,3,0,0))==NULL){
        modbus_free(mb);
        printf("new map failed: %s\n", modbus_strerror(errno));
        return 0;
    }

    mb_mapping->tab_registers[0]=0x1001;
    mb_mapping->tab_registers[1]=0x1002;
    mb_mapping->tab_registers[2]=0x1003;

    socket = modbus_tcp_listen(mb, 1);
    modbus_tcp_accept(mb, &socket);
    printf("create modbus slave success\n");

    while(1){
        do{                   /*轮询串口数据*/
            ret=modbus_receive(mb,query);
        }while(ret==0);
        if(ret>0){
            printf("len=%02d\t",ret);   /*%02d格式限定符,其中0表示变量宽度不足时以0作为填充,2表示显示宽度至少为2,d表示十进制整数*/
            for(int i=0; i<ret; ++i)
                printf("%02x", query[i]);
            printf("\n");
            modbus_reply(mb,query,ret,mb_mapping);
        }
        else{
            printf("quit the loop: %s\n", modbus_strerror(errno));
            break;
        }
    }
    modbus_mapping_free(mb_mapping);
    modbus_close(mb);
    free(query);
    modbus_free(mb);
    return 0;
}

2.2 主机demo

代码如下(示例):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "modbus.h"

int main(int argc, char *argv[]){
    uint16_t table[3];
    modbus_t *mt;

    mt = modbus_new_tcp("127.0.0.1", 1502);
    if(modbus_connect(mt)==-1){
        modbus_free(mt);
        printf("connect failed: %s\n", modbus_strerror(errno));
        return 0;
    }

    while(1){
        if(modbus_read_registers(mt, 0X0F, 3, table)==3)
            printf("read success: 0x%04x 0x%04x 0x%04x \n",table[0],table[1],table[2]);/*%04x格式限定符
            ,其中0表示变量宽度不足时以0作为填充,4表示显示宽度至少为4,x表示十六进制整数*/
        else{
            printf("read error: %s\n", modbus_strerror(errno));
            break;
        }
        for(int i=0; i<3; ++i)
            table[i]++;
        
        if(modbus_write_registers(mt, 0X0F, 3, table)==3)
            printf("write success: 0x%04x 0x%04x 0x%04x \n",table[0],table[1],table[2]);
        else{
            printf("write error: %s\n", modbus_strerror(errno));
            break;
        }
        Sleep(1000);
    }
    modbus_close(mt);
    modbus_free(mt);
    system("pause");
    return 0;
}

2.3 运行

先运行从机使之进入监听状态,再运行主机两者即进入通讯状态。
./master_demo.exe
read success: 0x1001 0x1002 0x1003 #主机读取从机对应寄存器的值
write success: 0x1002 0x1003 0x1004 #主机将对应寄存器的值处理后再写入从机的寄存器
read success: 0x1002 0x1003 0x1004 
write success: 0x1003 0x1004 0x1005 
read success: 0x1003 0x1004 0x1005 
write success: 0x1004 0x1005 0x1006 
read success: 0x1004 0x1005 0x1006 
write success: 0x1005 0x1006 0x1007 
#保存寄存器既可以读也可以写
./slave_demo.exe
create modbus slave success
len=12  000100000006ff03000f0003
len=19  00020000000dff10000f000306100210031004
len=12  000300000006ff03000f0003
len=19  00040000000dff10000f000306100310041005
len=12  000500000006ff03000f0003
len=19  00060000000dff10000f000306100410051006
len=12  000700000006ff03000f0003
len=19  00080000000dff10000f000306100510061007
quit the loop: No error

Demo中的读保持寄存器的报文格式(以从站第1条报文为例):

事务处理标识符协议标识符长度(字节)单元标识符功能码起始寄存器高位起始寄存器低位寄存器数量高位寄存器数量低位
2字节2字节2字节1字节1字节1字节1字节1字节1字节
000100000006ff03000f0003

释义:读取服务器(标识符0xff)保持寄存器(功能码0x03),从0x000f(起始寄存器高位+起始寄存器低位)开始的0x0003(寄存器数量高位+寄存器数量低位)个寄存器的值。


Demo中的写保持寄存器的报文格式(以从站第2条报文为例):

事务处理标识符协议标识符长度(字节)单元标识符功能码起始寄存器高位起始寄存器低位寄存器数量高位寄存器数量低位字节计数字节的值
2字节2字节2字节1字节1字节1字节1字节1字节1字节1字节字节计数
00020000000dff10000f000306100210031004

释义:写入服务器(标识符0xff)保持寄存器(功能码0x10),从0x000f(起始寄存器高位+起始寄存器低位)开始的0x0003(寄存器数量高位+寄存器数量低位)个寄存器,写入数值为0x1002、0x1003、0x1004(每个保持寄存器占2字节)。

3 输入寄存器测试

3.1 从机demo

在2.1示例中作如下修改:

//增加输入寄存器的映射
    if((mb_mapping=modbus_mapping_new_start_address(0,0,0,0,0x0f,0x03,0x20,0x03))==NULL){
        modbus_free(mb);
        printf("new map failed: %s\n", modbus_strerror(errno));
        return 0;
    }

    mb_mapping->tab_registers[0]=0x1001;
    mb_mapping->tab_registers[1]=0x1002;
    mb_mapping->tab_registers[2]=0x1003;
 #增加输入寄存器赋值
    mb_mapping->tab_input_registers[0]=0x1001;
    mb_mapping->tab_input_registers[1]=0x1002;
    mb_mapping->tab_input_registers[2]=0x1003;

3.2 主机demo

在2.2示例中作如下修改:

//将while(1)循环体内部代码段作如下修改
        if(modbus_read_input_registers(mt, 0X20, 3, table)==3)	#读取寄存器的函数需要改为modbus_read_input_registers(),若不修改将显示“Illegal data address”(非法数据地址),在LibModbus中每种寄存器的读写都有对应的方法,使用时它会检查寄存器地址是否合法
            printf("read success: 0x%04x 0x%04x 0x%04x \n",table[0],table[1],table[2]);/*%04x格式限定符
            ,其中0表示变量宽度不足时以0作为填充,4表示显示宽度至少为4,x表示十六进制整数*/
        else{
            printf("read error: %s\n", modbus_strerror(errno));
            break;
        }
        Sleep(1000);

3.3 运行

先运行从机使之进入监听状态,再运行主机两者即进入通讯状态。
./master_demo.exe
read success: 0x1001 0x1002 0x1003 
read success: 0x1001 0x1002 0x1003 
read success: 0x1001 0x1002 0x1003 
read success: 0x1001 0x1002 0x1003 
read success: 0x1001 0x1002 0x1003 
read success: 0x1001 0x1002 0x1003 
read success: 0x1001 0x1002 0x1003 
read success: 0x1001 0x1002 0x1003 
#输入寄存器只可读不可写
./slave_demo.exe
create modbus slave success
len=12  000100000006ff0400200003
len=12  000200000006ff0400200003
len=12  000300000006ff0400200003
len=12  000400000006ff0400200003
len=12  000500000006ff0400200003
len=12  000600000006ff0400200003
len=12  000700000006ff0400200003
len=12  000800000006ff0400200003

Demo中的读保持寄存器的报文格式(以从站第1条报文为例):

事务处理标识符协议标识符长度(字节)单元标识符功能码起始寄存器高位起始寄存器低位寄存器数量高位寄存器数量低位
2字节2字节2字节1字节1字节1字节1字节1字节1字节
000100000006ff0400200003

释义:读取服务器(标识符0xff)输入寄存器(功能码0x04)从0x0020(起始寄存器高位+起始寄存器低位)开始的0x0003(寄存器数量高位+寄存器数量低位)个寄存器的值。

4 线圈寄存器测试

4.1 从机demo

在3.1示例中作如下修改:

//增加线圈状态寄存器的映射
    if((mb_mapping=modbus_mapping_new_start_address(0x30,0x03,0,0,0x0f,0x03,0x20,0x03))==NULL){
        modbus_free(mb);
        printf("new map failed: %s\n", modbus_strerror(errno));
        return 0;
    }

    mb_mapping->tab_bits[0]=true;
    mb_mapping->tab_bits[1]=false;
    mb_mapping->tab_bits[2]=false;

4.2 主机demo

在3.2示例中作如下修改:

	uint8_t table[3]	//更改table数组的声明,libModbus中每个线圈寄存器占1字节
//将while(1)循环体内部代码段作如下修改	
        if(modbus_read_bits(mt, 0x30, 3, table)==3){   //读线圈寄存器状态
            printf("read success:\n");
        }
        else{
            printf("read error: %s\n", modbus_strerror(errno));
            break;
        }
        for(int i=0; i<3; ++i)
            printf("Line circuit %d: %s\n", i+1, table[i]?"ON":"OFF");

//将线圈寄存器值置反
        table[0]=table[0]?false:true;
        table[1]=table[1]?false:true;
        table[2]=table[2]?false:true;
        
        if(modbus_write_bits(mt, 0X30, 3, table)==3)	//写线圈寄存器状态
            printf("write success:\n");
        else{
            printf("write error: %s\n", modbus_strerror(errno));
            break;
        }

        printf("----------------------------------\n");
        Sleep(1000);

4.3 运行

先运行从机使之进入监听状态,再运行主机两者即进入通讯状态。
./master_demo.exe
read success:
Line circuit 1: ON
Line circuit 2: OFF
Line circuit 3: OFF
write success:
----------------------------------
read success:
Line circuit 1: OFF
Line circuit 2: ON
Line circuit 3: ON
write success:
----------------------------------
read success:
Line circuit 1: ON
Line circuit 2: OFF
Line circuit 3: OFF
write success:
#线圈寄存器支持读和写
----------------------------------
./slave_demo.exe
create modbus slave success
len=12  000100000006ff0100300003
len=14  000200000008ff0f003000030106
len=12  000300000006ff0100300003
len=14  000400000008ff0f003000030101
len=12  000500000006ff0100300003
len=14  000600000008ff0f003000030106

Demo中的读线圈寄存器的报文格式(以从站第1条报文为例):

事务处理标识符协议标识符长度(字节)单元标识符功能码起始寄存器高位起始寄存器低位寄存器数量高位寄存器数量低位
2字节2字节2字节1字节1字节1字节1字节1字节1字节
000100000006ff0100300003

释义:读取服务器(标识符0xff)线圈寄存器(功能码0x01)从0x0030(起始寄存器高位+起始寄存器低位)开始的0x0003(寄存器数量高位+寄存器数量低位)个线圈寄存器的状态值(开true或关false)。


Demo中的写线圈寄存器的报文格式(以从站第2条报文为例):

事务处理标识符协议标识符长度(字节)单元标识符功能码起始寄存器高位起始寄存器低位寄存器数量高位寄存器数量低位字节计数字节的值
2字节2字节2字节1字节1字节1字节1字节1字节1字节1字节字节计数
000200000008ff0f003000030106

释义:写入服务器(标识符0xff)保持寄存器(功能码0x10),从0x000f(起始寄存器高位+起始寄存器低位)开始的0x0003(寄存器数量高位+寄存器数量低位)个寄存器,写入寄存器状态0x06(00000110,由低位开始算第一个线圈0,第二个线圈1,第3个线圈1)。
注意!Modbus报文中线圈是以bit位为存储单元的,而在libmodbus从机镜像中线圈是以byte字节为存储单元的。

5 离散输入状态寄存器测试

5.1 从机demo

在4.1示例中作如下修改:

//增加离散输入线圈状态寄存器的映射
    if((mb_mapping=modbus_mapping_new_start_address(0x30,0x08,0x00,0x08,0x0f,0x03,0x20,0x03))==NULL){
        modbus_free(mb);
        printf("new map failed: %s\n", modbus_strerror(errno));
        return 0;
    }
    mb_mapping->tab_input_bits[0]=true;
    mb_mapping->tab_input_bits[1]=false;
    mb_mapping->tab_input_bits[2]=false;
    mb_mapping->tab_input_bits[3]=true;
    mb_mapping->tab_input_bits[4]=false;
    mb_mapping->tab_input_bits[5]=false;
    mb_mapping->tab_input_bits[6]=false;
    mb_mapping->tab_input_bits[7]=false;

5.2 主机demo

在4.2示例中作如下修改:

//离散输入线圈的读取使用modbus_read_input_bits()函数
        if(modbus_read_input_bits(mt, 0x00, 8, table)==8){
            printf("read success:\n");
        }

5.3 运行

先运行从机使之进入监听状态,再运行主机两者即进入通讯状态。
./master_demo.exe
read success:
Line circuit 1: ON
Line circuit 2: OFF
Line circuit 3: OFF
Line circuit 4: ON
Line circuit 5: OFF
Line circuit 6: OFF
Line circuit 7: OFF
Line circuit 8: OFF
./slave_demo.exe
create modbus slave success
len=12  000100000006ff0200000008
len=12  000200000006ff0200000008
len=12  000300000006ff0200000008
len=12  000400000006ff0200000008

Demo中的读离散输入线圈寄存器的报文格式(以从站第1条报文为例):

事务处理标识符协议标识符长度(字节)单元标识符功能码起始寄存器高位起始寄存器低位寄存器数量高位寄存器数量低位
2字节2字节2字节1字节1字节1字节1字节1字节1字节
000100000006ff0200000008

释义:读取服务器(标识符0xff)的离散输入线圈寄存器(功能码0x02)从0x0000(起始寄存器高位+起始寄存器低位)开始的0x0008(寄存器数量高位+寄存器数量低位)个线圈寄存器的状态值(开true或关false)。

总结

主从机的定义、通讯建立、从机寄存器镜像的存储结构、线圈寄存器读取和写入、离散输入寄存器的读取、保持寄存器的读取和写入、输入寄存器的读取是Modbus通讯开发的基础,本文以ModbusTCP示例演示了libmodbus库相关接口的使用,希望对看到这篇博文的大家学习Modbus有所帮助。在这篇博文编辑的过程中发现LibModbus库中RTU从机无法进入监听,运行至connect()函数直接跳出,并无errno,希望路过的大神有了解的指正一下。

  • 17
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZW_finder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值