libmodbus笔记
@@ date : 2026.4.20
@@ ps : 一些开源工程libmodbus的源码学习笔记
网址:https://github.com/stephane/libmodbus
参考书籍:《modbus软件开发实战指南》
modbus初始化
这里主从都差不多。
主函数中首先确定使用的modbus类型,并根据类型确定使用的设备相关特征,对于modbus-tcp就是ip
c
if (strcmp(argv[1], "tcp") == 0) {
use_backend = TCP;
} else if (strcmp(argv[1], "tcppi") == 0) {
use_backend = TCP_PI;
} else if (strcmp(argv[1], "rtu") == 0) {
use_backend = RTU;
......
switch (use_backend) {
case TCP:
ip_or_device = "127.0.0.1";
break;
case TCP_PI:
ip_or_device = "::1";
break;
case RTU:
ip_or_device = "/dev/ttyUSB1";
break;
}
根据使用的ip_or_device创建modbus总线,并设置要访问的从机ID
c
if (use_backend == TCP) {
ctx = modbus_new_tcp(ip_or_device, 1502);
} else if (use_backend == TCP_PI) {
ctx = modbus_new_tcp_pi(ip_or_device, "1502");
} else {
ctx = modbus_new_rtu(ip_or_device, 115200, 'N', 8, 1);
}
。。。。。
if (use_backend == RTU) {
modbus_set_slave(ctx, SERVER_ID);
}
然后就是连接了。
其中有一个关键结构体就是函数modbus_new_rtu()创建的modbus_t *ctx,这个结构体中包含了所有需要使用到的东西,其modbus_backend_t是一堆函数指针,也就是完成各类modbus协议操作的函数。
c
struct _modbus {
/* Slave address */
int slave;
/* Socket or file descriptor */
int s;
int debug;
int error_recovery;
int quirks;
struct timeval response_timeout;
struct timeval byte_timeout;
struct timeval indication_timeout;
const modbus_backend_t *backend;
void *backend_data;
};
typedef struct _modbus_backend {
unsigned int backend_type;
unsigned int header_length;
unsigned int checksum_length;
unsigned int max_adu_length;
int (*set_slave)(modbus_t *ctx, int slave);
int (*build_request_basis)(
modbus_t *ctx, int function, int addr, int nb, uint8_t *req);
int (*build_response_basis)(sft_t *sft, uint8_t *rsp);
int (*prepare_response_tid)(const uint8_t *req, int *req_length);
int (*send_msg_pre)(uint8_t *req, int req_length);
ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length);
int (*receive)(modbus_t *ctx, uint8_t *req);
ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length);
int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length);
int (*pre_check_confirmation)(modbus_t *ctx,
const uint8_t *req,
const uint8_t *rsp,
int rsp_length);
int (*connect)(modbus_t *ctx);
unsigned int (*is_connected)(modbus_t *ctx);
void (*close)(modbus_t *ctx);
int (*flush)(modbus_t *ctx);
int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
void (*free)(modbus_t *ctx);
} modbus_backend_t;
寄存器读写
对于写数据,文件modbus.c中给出了各类寄存器的写函数,如下:

进入这些函数就会发现,所有的函数都调用了send_msg()函数,在这个函数中调用了上面backend中的各类接口函数。
对于MODBUS-RTU就是这些函数了:
const modbus_backend_t _modbus_rtu_backend = {
_MODBUS_BACKEND_TYPE_RTU,
_MODBUS_RTU_HEADER_LENGTH,
_MODBUS_RTU_CHECKSUM_LENGTH,
MODBUS_RTU_MAX_ADU_LENGTH,
_modbus_set_slave,
_modbus_rtu_build_request_basis,
_modbus_rtu_build_response_basis,
_modbus_rtu_prepare_response_tid,
_modbus_rtu_send_msg_pre,
_modbus_rtu_send,
_modbus_rtu_receive,
_modbus_rtu_recv,
_modbus_rtu_check_integrity,
_modbus_rtu_pre_check_confirmation,
_modbus_rtu_connect,
_modbus_rtu_is_connected,
_modbus_rtu_close,
_modbus_rtu_flush,
_modbus_rtu_select,
_modbus_rtu_free
};
关键函数
来看一下modbus_new_rtu()函数中具体干了什么,其中只调用了一个函数_modbus_init_common,这个函数中对ctx中的一些基本成员进行初始化,剩下的都是直接对ctx结构体成员进行初始化,比如下面这些
c
ctx_rtu->baud = baud;
if (parity == 'N' || parity == 'E' || parity == 'O') {
ctx_rtu->parity = parity;
} else {
modbus_free(ctx);
errno = EINVAL;
return NULL;
}
ctx_rtu->data_bit = data_bit;
ctx_rtu->stop_bit = stop_bit;
#if HAVE_DECL_TIOCSRS485
/* The RS232 mode has been set by default */
ctx_rtu->serial_mode = MODBUS_RTU_RS232;
#endif
#if HAVE_DECL_TIOCM_RTS
/* The RTS use has been set by default */
ctx_rtu->rts = MODBUS_RTU_RTS_NONE;
/* Calculate estimated time in micro second to send one byte */
ctx_rtu->onebyte_time =
1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;
/* The internal function is used by default to set RTS */
ctx_rtu->set_rts = _modbus_rtu_ioctl_rts;
/* The delay before and after transmission when toggling the RTS pin */
ctx_rtu->rts_delay = ctx_rtu->onebyte_time;
#endif
ctx_rtu->confirmation_to_ignore = FALSE;
最关键的一部就是对backend进行设置ctx->backend = &_modbus_rtu_backend;.
举个例子,当我们设置从设备ID时调用下面的函数,其实就是调用了backend中的set_slave函数指针指向的函数,该函数就是上面的_modbus_rtu_backend中的_modbus_set_slave函数。
c
int modbus_set_slave(modbus_t *ctx, int slave)
{
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
return ctx->backend->set_slave(ctx, slave);
}
以从设备的接收为例,其使用下面这个函数进行接收:
c
int modbus_receive(modbus_t *ctx, uint8_t *req)
{
if (ctx == NULL) {
errno = EINVAL;
return -1;
}
return ctx->backend->receive(ctx, req);
}
可以看到,仍然使用的是backend中的receive函数,也就是下面这个函数,其真正的主体调用了_modbus_receive_msg函数
c
static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
{
int rc;
modbus_rtu_t *ctx_rtu = ctx->backend_data;
if (ctx_rtu->confirmation_to_ignore) {
_modbus_receive_msg(ctx, req, MSG_CONFIRMATION);
/* Ignore errors and reset the flag */
ctx_rtu->confirmation_to_ignore = FALSE;
rc = 0;
if (ctx->debug) {
printf("Confirmation to ignore\n");
}
} else {
rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
if (rc == 0) {
/* The next expected message is a confirmation to ignore */
ctx_rtu->confirmation_to_ignore = TRUE;
}
}
return rc;
}