libmodbus笔记
@@ date : 2026.4.20
@@ ps : 一些开源工程libmodbus的源码学习笔记
网址:https://github.com/stephane/libmodbus
参考书籍:《modbus软件开发实战指南》
modbus初始化
这里主从都差不多。
主函数中首先确定使用的modbus类型,并根据类型确定使用的设备相关特征,对于modbus-tcp就是ip
if(strcmp(argv[1],"tcp")==0){use_backend=TCP;}elseif(strcmp(argv[1],"tcppi")==0){use_backend=TCP_PI;}elseif(strcmp(argv[1],"rtu")==0){use_backend=RTU;......switch(use_backend){caseTCP:ip_or_device="127.0.0.1";break;caseTCP_PI:ip_or_device="::1";break;caseRTU:ip_or_device="/dev/ttyUSB1";break;}根据使用的ip_or_device创建modbus总线,并设置要访问的从机ID
if(use_backend==TCP){ctx=modbus_new_tcp(ip_or_device,1502);}elseif(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协议操作的函数。
struct_modbus{/* Slave address */intslave;/* Socket or file descriptor */ints;intdebug;interror_recovery;intquirks;structtimevalresponse_timeout;structtimevalbyte_timeout;structtimevalindication_timeout;constmodbus_backend_t*backend;void*backend_data;};typedefstruct_modbus_backend{unsignedintbackend_type;unsignedintheader_length;unsignedintchecksum_length;unsignedintmax_adu_length;int(*set_slave)(modbus_t*ctx,intslave);int(*build_request_basis)(modbus_t*ctx,intfunction,intaddr,intnb,uint8_t*req);int(*build_response_basis)(sft_t*sft,uint8_t*rsp);int(*prepare_response_tid)(constuint8_t*req,int*req_length);int(*send_msg_pre)(uint8_t*req,intreq_length);ssize_t(*send)(modbus_t*ctx,constuint8_t*req,intreq_length);int(*receive)(modbus_t*ctx,uint8_t*req);ssize_t(*recv)(modbus_t*ctx,uint8_t*rsp,intrsp_length);int(*check_integrity)(modbus_t*ctx,uint8_t*msg,constintmsg_length);int(*pre_check_confirmation)(modbus_t*ctx,constuint8_t*req,constuint8_t*rsp,intrsp_length);int(*connect)(modbus_t*ctx);unsignedint(*is_connected)(modbus_t*ctx);void(*close)(modbus_t*ctx);int(*flush)(modbus_t*ctx);int(*select)(modbus_t*ctx,fd_set*rset,structtimeval*tv,intmsg_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结构体成员进行初始化,比如下面这些
ctx_rtu->baud=baud;if(parity=='N'||parity=='E'||parity=='O'){ctx_rtu->parity=parity;}else{modbus_free(ctx);errno=EINVAL;returnNULL;}ctx_rtu->data_bit=data_bit;ctx_rtu->stop_bit=stop_bit;#ifHAVE_DECL_TIOCSRS485/* The RS232 mode has been set by default */ctx_rtu->serial_mode=MODBUS_RTU_RS232;#endif#ifHAVE_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;#endifctx_rtu->confirmation_to_ignore=FALSE;最关键的一部就是对backend进行设置ctx->backend = &_modbus_rtu_backend;.
举个例子,当我们设置从设备ID时调用下面的函数,其实就是调用了backend中的set_slave函数指针指向的函数,该函数就是上面的_modbus_rtu_backend中的_modbus_set_slave函数。
intmodbus_set_slave(modbus_t*ctx,intslave){if(ctx==NULL){errno=EINVAL;return-1;}returnctx->backend->set_slave(ctx,slave);}以从设备的接收为例,其使用下面这个函数进行接收:
intmodbus_receive(modbus_t*ctx,uint8_t*req){if(ctx==NULL){errno=EINVAL;return-1;}returnctx->backend->receive(ctx,req);}可以看到,仍然使用的是backend中的receive函数,也就是下面这个函数,其真正的主体调用了_modbus_receive_msg函数
staticint_modbus_rtu_receive(modbus_t*ctx,uint8_t*req){intrc;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;}}returnrc;}