概述
最近想给网关机增加一个modbus_tcp子站(slave),本来准备自己写一个,了解了一下开源库libmodbus觉得相当不错。
- 采用C语言开发,支持跨平台,Linux, Mac OS X, FreeBSD, QNX and Win32。与Qt开发的程序可以完美结合
- 支持TCP、RTU两种模式,支持Slave和Monster
- 支持多联机,采用的select模式,性能各方面肯定没问题
- 有完善的说明文档 官方文档
安装编译
直接在github或者官网下载,官网见上面,我选择的是3.1.6版本,编译环境是Windows系统,编译器是VS2010
- 打开根目录下的说明README.md,上面写着各个平台的编译方法,Windows编译是打开src/win32 下面的文件,
To compile under Windows, install [MinGW](http://www.mingw.org/) and MSYS then
select the common packages (gcc, automake, libtool, etc). The directory
*./src/win32/* contains a Visual C project.
- 用VS2010打开工程文件,会提示升级,直接编译会失败,提示找不到config.h文件;
- 在win32目录有一个编译说明,上面说明了怎么生成config.h文件。在当前目录找到configure.js文件,双击就生成config文件了,再编译就好了。
config.h and ../modbus-version.h are generated using configure.js.
Run
cscript configure.js
or
wscript configure.js
or
double click configure.js to generate these files.
- 其他平台的编译类似,看编译说明。当然也可以直接把源文件添加到工程
使用说明
下面以modbus-tcp-slave为例,来说明libmodbus的用法。框中的第一个是能支持多个连接的模式,采用的select模式,第二个是单连接,其他的用法都一样。下面以bandwidth-server-one.c为例进行说明。
2. 应用库的时候,记得要添加socket的库,在头文件前面添加下面的语句
#pragma comment(lib,"ws2_32.lib")
- 几个重要的函数
#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <modbus.h>
#pragma comment(lib,"ws2_32.lib")
#if defined(_WIN32)
#define close closesocket
#endif
enum {
TCP,
RTU
};
int main(int argc, char *argv[])
{
int s = -1;
modbus_t *ctx = NULL;
modbus_mapping_t *mb_mapping = NULL; //寄存器管理
int rc;
int use_backend;
/* TCP */
if (argc > 1) {
if (strcmp(argv[1], "tcp") == 0) {
use_backend = TCP;
} else if (strcmp(argv[1], "rtu") == 0) {
use_backend = RTU;
} else {
printf("Usage:\n %s [tcp|rtu] - Modbus client to measure data bandwidth\n\n", argv[0]);
exit(1);
}
} else {
/* By default */
use_backend = TCP;
}
if (use_backend == TCP) {
ctx = modbus_new_tcp("127.0.0.1", 1502); //监听端口,默认是502
s = modbus_tcp_listen(ctx, 1);
modbus_tcp_accept(ctx, &s);
} else {
ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1);
modbus_set_slave(ctx, 1);
modbus_connect(ctx);
}
//这个要重点关注,设置寄存器和线圈的个数,起始地址为0,
//如果不想从0开始,可以用这个方法modbus_mapping_new_start_address
mb_mapping = modbus_mapping_new(MODBUS_MAX_READ_BITS, 0,
MODBUS_MAX_READ_REGISTERS, 0);
if (mb_mapping == NULL) {
fprintf(stderr, "Failed to allocate the mapping: %s\n",
modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
//modbus的数据收发
for(;;) {
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
rc = modbus_receive(ctx, query);
if (rc > 0) {
modbus_reply(ctx, query, rc, mb_mapping);
} else if (rc == -1) {
/* Connection closed by the client or error */
break;
}
}
printf("Quit the loop: %s\n", modbus_strerror(errno));
modbus_mapping_free(mb_mapping);
if (s != -1) {
close(s);
}
/* For RTU, skipped by TCP (no TCP connect) */
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
- 如何与应用结合。modbus_mapping_t 结构体里面存储了所有的寄存器和线圈,只需要将应用的值更新到里面就可以,通过读取保持寄存器就可以获取Monster写入的值。
typedef struct _modbus_mapping_t {
int nb_bits;
int start_bits;
int nb_input_bits;
int start_input_bits;
int nb_input_registers;
int start_input_registers;
int nb_registers;
int start_registers;
uint8_t *tab_bits; //线圈寄存器
uint8_t *tab_input_bits; //输入线圈寄存器
uint16_t *tab_input_registers; //输入寄存器
uint16_t *tab_registers; //保持寄存器
} modbus_mapping_t;