这篇文章是接上一篇Modbus协议简介,介绍Modbus实际项目应用,断断续续写了近两周时间。
为什么要使用驱动库?
上一篇文章,我们介绍了Modbus协议物理层和协议层,我们知道了Modbus是一种总线协议,它可以基于串口或网口,以基于串口的Modbus-RTU为例,我们需要在Windows或Linux下实现一个上位机,上位机的功能是读写Modbus接口传感器设备的数据,或者是和单片机等从设备进行交互。
当需要向某个从机寄存器写入某个值时,如向01地址的设备,0x0105保持寄存器写入1个数据:0x0190为例,那么需要构建这样一个数据帧:
主机发送: 01 06 01 05 01 90 99 CB
01表示从机地址,06功能码表示写单个保持寄存器,0105表示寄存器地址,0190表示写入寄存器的数值,99 CB为CRC校验值。
如果从机正确的收到了数据,会回复一个数据帧:
从机回复: 01 06 01 05 01 90 99 CB
所以作为主机,写数据的流程是:
-
构建一个Modbus-RTU数据帧 -
等待从机响应的数据 -
如果响应数据正确,说明写入成功,否则写入失败。
读数据也是同样的流程,我们可以基于串口发送、串口接收函数、定时器等,自己写一个Modbus驱动库,来实现对从设备的读写。当然,也可以直接使用别人写好的Modbus驱动库,比如libmodbus,本文将介绍如何使用libmodbus驱动库,实现Modbus主机和从机。
libmodbus简介
libmodbus,是一个基于C语言实现的Modbus驱动库,作者是Stephane,支持Linux, Mac OS X, FreeBSD, QNX and Win32操作系统,主要应用在PC上,用来开发上位机,也可以对源代码进行交叉编译,以适配更多的平台,比如ARM Linux。源代码开源,遵循 LGPL-2.1 许可。目前最新版本是3.1.6,Github仓库最新提交时间是2021年5月21日。
官方网站:https://libmodbus.org/
开源地址:https://github.com/stephane/libmodbus/
libmodbus支持如下功能:
- 支持Modbus-RTU和Modbus-TCP
- 支持常用功能码,如01/02/03/04/05/06/07/0F/10/11/16/17
- 支持线圈类型读写、寄存器读写、离散量读取等
- 支持广播地址0,从机地址1-247
- 支持浮点数和整形数据转换,大端小端等多种模式
- 参数根据
Modbus_Application_Protocol_V1_1b.pdf 官方标准文档设计,比如最大读写线圈个数,最大读写寄存器个数等。 - 源代码基于C编写,方便在各平台移植,只有11个文件。
libmodbus常用函数
libmodbus库函数非常简洁,读写操作函数对于RTU和TCP完全通用,RTU和TCP切换只需要修改一行代码就可以实现无缝切换。
modbus_t *mb;
int ret;
mb = modbus_new_rtu("/dev/ttySP1", 115200, 'N', 8, 1);
mb = modbus_new_rtu("COM1", 115200, 'N', 8, 1);
mb = modbus_new_tcp("127.0.0.1", 502);
ret = modbus_set_slave(mb, slave);
ret = modbus_connect(mb);
ret = modbus_set_response_timeout(mb, 1, 200000);
uint16_t *table;
ret = modbus_read_registers(mb, 2, 5, table);
modbus_close(mb);
modbus_free(mb);
ret = modbus_write_register(mb, 2, 56);
uint16_t table[5] = {11, 22, 33, 44, 55};
ret = modbus_write_registers(mb, 12, 5, table);
ret = modbus_write_bit(mb, 11, TRUE);
char *err_str;
err_str = modbus_strerror(errno);
Windows平台libmodbus 使用
以Windows下使用libmodbus实现从机和主机为例,Linux下类似。
1.获取源代码
使用Git工具下载GitHub代码仓库源代码到本地,这样可以获取到最新的libmodbus代码,但是也会有一些Bug。
git clone https://github.com/stephane/libmodbus/
如果下载速度缓慢,可以到我的Gitee仓库下载:
git clone https://gitee.com/whik/libmodbus
或者到官方仓库下载最新稳定发布版本v3.1.6:
https://libmodbus.org/releases/libmodbus-3.1.6.tar.gz
下载完成之后,解压到本地,Linux系统可以使用tar -zxvf libmodbus-3.0.6.tar.gz 命令行解压:
我们重点关注以下3个文件夹:doc,src,tests。
- doc,doc文件夹包含库的使用文档,文件名就是函数名,介绍每个函数的使用方法,参数定制,返回值说明,示例代码等。
- src,src文件夹是libmodbus库源文件和头文件,我们只需要把这些文件添加到工程中,然后包含头文件就可以直接使用了。
- tests,tests文件夹包含libmodbus使用示例,
包括Modbus-RTU/TCP客户端和服务器单元测试,随机测试,效率测试,读写10万个线圈状态,10万个寄存器,记录消耗时间。
nb_points = MODBUS_MAX_READ_BITS;
start = gettime_ms();
for (i=0; i<n_loop; i++) {
rc = modbus_read_bits(ctx, 0, nb_points, tab_bit);
if (rc == -1) {
fprintf(stderr, "%s\n", modbus_strerror(errno));
return -1;
}
}
end = gettime_ms();
elapsed = end - start;
官方提供的测试代码太繁琐,后面我们会写两个简单的示例程序,来演示主机和从机的使用。
2.生成config.h配置文件
无论是Windows还是Linux,在使用libmodbus库之前,我们需要先调用configure工具来生成config.h文件和Makefile。configure工具会根据当前系统环境,生成适用于当前平台的config.h文件。
在libmodbus库文件夹下执行./configure 命令。
whik@windows_7 MINGW64 /d/libmodbus-3.1.6
$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for strings.h... yes
.......
checking for inttypes.h... yes
config.status: creating tests/unit-test.h
config.status: executing libtool commands
libmodbus 3.1.6
===============
prefix: /usr/local
sysconfdir: ${prefix}/etc
libdir: ${exec_prefix}/lib
includedir: ${prefix}/include
compiler: gcc
cflags: -g -O2
ldflags:
documentation: no
tests: yes
整个过程需要1分钟左右的时间,等待运行完成之后,会发现在当前目录下多了一些文件,主要是config.h 和Makefile
如果想使用libmodbus官方提供的测试代码,可以直接在根目录执行make 命令,就可以直接编译tests目录下的测试代码,Linux系统可以使用make install 命令进行和安装。
3.编写测试代码
新建一个文件夹my_test ,把libmodbus/src文件夹中的.c和.h文件,config.h复制到my_test。
学习了libmodbus常用函数之后,我们就可以写一个简单的测试代码了。
Modbus-RTU主机测试:test_rtu_master.c,实现对地址为1的从机设备,读取地址15/16/17的保持寄存器数据,进行+1操作后,再写入。
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "modbus.h"
#define PORT_NAME "COM1"
int main(int argc, char *argv[])
{
int ret;
uint16_t table[3];
modbus_t *mb;
char port[20];
printf("argc = %d, argv[1] = %s\n", argc, argv[1]);
if(argc == 2)
strcpy(port, argv[1]);
else
strcpy(port, PORT_NAME);
printf("libmodbus modbu-rtu master demo: %s, 115200, N, 8, 1\n", port);
mb = modbus_new_rtu(port, 115200, 'N', 8, 1);
if (mb == NULL)
{
modbus_free(mb);
printf("new rtu failed: %s\n", modbus_strerror(errno));
return 0;
}
modbus_set_slave(mb, 1);
ret = modbus_connect(mb);
if(ret == -1)
{
modbus_close(mb);
modbus_free(mb);
printf("connect failed: %s\n", modbus_strerror(errno));
return 0;
}
while(1)
{
ret = modbus_read_registers(mb, 0x0F, 3, table);
if(ret == 3)
printf("read success : 0x%02x 0x%02x 0x%02x \n", table[0], table[1], table[2]);
else
{
printf("read error: %s\n", modbus_strerror(errno));
break;
}
for(int i = 0; i < 3; i++)
table[i] += 1;
ret = modbus_write_registers(mb, 0x0F, 3, table);
if(ret == 3)
printf("write success: 0x%02x 0x%02x 0x%02x \n", table[0], table[1], table[2]);
else
{
printf("write error: %s\n", modbus_strerror(errno));
break;
}
Sleep(1000);
}
modbus_close(mb);
modbus_free(mb);
system("pause");
return 0;
}
Modbus-RTU从机测试:test_rtu_slave.c,创建从机设备,地址为1,初始化了3个保持寄存器,地址分别为15/16/17,数据分别为0x1001/0x1002/0x1003。
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "modbus.h"
#define PORT_NAME "COM2"
int main(int argc, char *argv[])
{
int ret = 0;
uint8_t device = 1;
uint8_t *query;
modbus_t *mb;
modbus_mapping_t *mb_mapping;
char port[20];
printf("argc = %d, argv[1] = %s\n", argc, argv[1]);
if(argc == 2)
strcpy(port, argv[1]);
else
strcpy(port, PORT_NAME);
printf("libmodbus modbu-rtu slave demo: %s, 115200, N, 8, 1\n", port);
mb = modbus_new_rtu(port, 115200, 'N', 8, 1);
if (mb == NULL)
{
modbus_free(mb);
printf("new rtu failed: %s\n", modbus_strerror(errno));
return 0;
}
mb_mapping = modbus_mapping_new_start_address(0, 0, 0, 0, 15, 3, 0, 0);
if(mb_mapping == NULL)
{
modbus_free(mb);
printf("new mapping 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;
modbus_set_slave(mb, device);
ret = modbus_connect(mb);
if(ret == -1)
{
modbus_free(mb);
printf("connect failed: %s\n", modbus_strerror(errno));
return 0;
}
printf("create modbus slave success\n");
while(1)
{
do {
ret = modbus_receive(mb, query);
} while (ret == 0);
if(ret > 0)
{
printf("len=%02d: ", ret);
for(int idx = 0; idx < ret; idx++)
{
printf(" %02x", query[idx]);
}
printf("\n");
modbus_reply(mb, query, ret, mb_mapping);
}
else
{
printf("quit the loop: %s", modbus_strerror(errno));
modbus_mapping_free(mb_mapping);
break;
}
}
modbus_close(mb);
modbus_free(mb);
return 0;
}
4.编译测试代码
现学了Makefile语法,凑合用。需要注意的是,windows下libmodbus依赖于ws2_32.dll库,需要添加编译参数-lws2_32:
.PHONY: all
all: test_rtu_master test_rtu_slave
test_rtu_master : test_rtu_master.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o
gcc test_rtu_master.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_master -lws2_32
test_rtu_slave : test_rtu_slave.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o
gcc test_rtu_slave.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_slave -lws2_32
test_rtu_slave.o : test_rtu_slave.c
gcc test_rtu_slave.c -c -I.
test_rtu_master.o : test_rtu_master.c
gcc test_rtu_master.c -c -I.
modbus.o : modbus.c
gcc modbus.c -c -I.
modbus-rtu.o : modbus-rtu.c
gcc modbus-rtu.c -c -I.
modbus-tcp.o : modbus-tcp.c
gcc modbus-tcp.c -c -I.
modbus-data.o : modbus-data.c
gcc modbus-data.c -c -I.
clean:
rm -rf *.o *.exe
最终的文件目录:
Windows下Make工具我使用的是Qt自带的mingw32-make.exe工具,位于\Qt5.7.0\Tools\mingw530_32\bin 目录下,执行mingw32-make 命令进行,会对两个测试文件进行编译:
whik@Windows_7 MINGW64 /d/my_test
$ mingw32-make.exe
gcc test_rtu_master.c -c -I.
gcc modbus.c -c -I.
gcc modbus-tcp.c -c -I.
gcc modbus-rtu.c -c -I.
gcc modbus-data.c -c -I.
In file included from modbus-data.c:24:0:
./config.h:171:0: warning: "WINVER" redefined
#define WINVER 0x0501
^
In file included from D:/Program/Qt5.7.0/Tools/mingw530_32/i686-w64-mingw32/include/windows.h:10:0,
from D:/Program/Qt5.7.0/Tools/mingw530_32/i686-w64-mingw32/include/winsock2.h:23,
from modbus-data.c:19:
D:/Program/Qt5.7.0/Tools/mingw530_32/i686-w64-mingw32/include/sdkddkver.h:162:0: note: this is the location of the previous definition
#define WINVER _WIN32_WINNT
^
gcc test_rtu_master.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_master -lws2_32
gcc test_rtu_slave.c -c -I.
gcc test_rtu_slave.o modbus.o modbus-tcp.o modbus-rtu.o modbus-data.o -o test_rtu_slave -lws2_32
会在当前目录下生成目标文件:test_rtu_master.exe 和test_rtu_slave.exe 。
这里,我的电脑本机虚拟了两个串口COM1和COM2,两个串口直接进行连接。
先启动从机设备,配置为COM1:
$ ./test_rtu_slave.exe "COM1"
再启动主机设备,配置为COM2:
$ ./test_rtu_master.exe "COM2"
可以看到,从机可以正确的对接收的数据帧进行相应,主机可以正确的进行读取和写入。
如果需要测试Modbus-TCP,只需要修改modbus设备创建函数:
mb = modbus_new_rtu(port, 115200, 'N', 8, 1);
mb = modbus_new_tcp("127.0.0.120", 502);
其他无需任何改动!
Linux平台下libmodbus使用
Ubuntu下使用libmodbus和Windows几乎一样:
tar -zxvf libmodbus-3.0.6.tar.gz
./configure
make
make install
测试文件和Windows几乎一样,不过不需要ws2_32库的支持了。
(来自:blog.csdn.net/qq_30650153/article/details/83385626)
ARM平台下libmodbus使用
ARM开发板下使用libmodbus,需要使用交叉编译器进行交叉编译,生成so库文件。
1.解压:
tar -zxvf libmodbus-3.0.6.tar.gz
2.创建安装目录:
mkdir install
3.配置编译选项:
./configure --host=arm-fsl-linux-gnueabi --enable-static --prefix=[安装路径]/install/
4.编译:make
5.安装:make install
在install目录会生成3个文件夹:include lib share
进入install/lib目录,执行file libmodbus*,出现如下打印信息,信息中有“ARM”说明libmodbus库移植成功。
libmodbus.a: current ar archive
libmodbus.la: libtool library file,
libmodbus.so: symbolic link to `libmodbus.so.5.0.5'
libmodbus.so.5: symbolic link to `libmodbus.so.5.0.5'
libmodbus.so.5.0.5: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, not stripped
将libmodbus.so、libmodbus.so.5、libmodbus.so.5.0.5 复制到ARM开发板中的/usr/lib 目录下
执行cp libmodbus.so* /usr/lib
如果出现无法创建的问题(cannot create ‘/usr/lib/libmodbus.so*’: Read-only file system)。
可以执行 wr cp libmodbus* /usr/lib
测试与使用,和Windows一样,对测试文件使用ARM交叉编译器进行编译。
(来自:www.cnblogs.com/happybirthdaytoyou/p/11301612.html)
libmodbus 从机地址限制的问题
libmodbus支持1-247从机地址,0为广播地址,但是有些非标准的Modbus传感器,并不是采用0作为广播地址,而是0xfe作为广播地址:
所以使用libmodbus会出现报错终止运行的问题,这是因为libmodbus源代码中限制了从机地址1-247,我们只需要修改源代码即可。
modbus-rtu.c文件95行:
modbus-tcp.c文件80行:
只需要修改这两个数值就可以取消从机地址限制的问题。
详细的从站最大地址限制问题排查记录,可以查看:
https://blog.csdn.net/qingzhuyuxian/article/details/80391553
其实这个问题,早在2011年,就有人在官方GitHub仓库提Issues了:
https://github.com/stephane/libmodbus/issues/38
对此问题,作者的答复是,为了遵循Modbus官方标准,所以一直以来都没有进行修改。
|