樱落之时,心之所向:邂逅那一抹倾城的“樱花漫画”

核心内容摘要

征服二次元的视觉盛宴:雷火剑动漫OVA免费高清全解析!
17c在线观看免费:点亮你的数字娱乐新纪元

枫叶染红的铃声:重温《铃与枫》第一季的浪漫与心动

目录

前言

分析场景modbus_write_bits 执行流程

第一步创建 RTU 上下文modbus_new_rtu

第二步设置从设备地址modbus_set_slave

第三步建立串口连接modbus_connect

第四步构造写请求modbus_write_bits

第五步发送请求报文send_msg 系列函数

RTU 后端核心结构解读

九、

总结

结尾

前言在上一篇笔记中我们完成了 libmodbus 库的介绍、源码获取与阅读工具的实操为深入研读源码打下了基础。

libmodbus 的

核心价值在于封装了 Modbus 协议的底层细节而发送请求是 Modbus 主设备的核心操作之一。

本次笔记将以modbus_write_bits写多个线圈函数为例拆解 libmodbus 发送请求的完整执行流程深入解析每一步的源码逻辑与核心结构体作用帮助你理解 libmodbus 的底层封装思路为后续的二次开发与裸机移植提供理论支撑。

分析场景modbus_write_bits 执行流程本次源码分析聚焦于 Modbus 主设备发送写请求的场景以modbus_write_bits函数为核心线索逐步拆解从上下文创建到报文发送的完整流程整个流程分为五大核心步骤每一步对应 libmodbus 的关键 API 与底层实现。

补充modbus_write_bits函数用于向 Modbus 从设备写入多个线圈状态对应 1 位数字量输出是工业控制中常用的写操作 API其底层封装了 Modbus RTU 报文的构造、校验与发送逻辑。

第一步创建 RTU 上下文modbus_new_rtu主设备发送请求的第一步是调用modbus_new_rtu函数创建 Modbus RTU 模式的上下文该函数的核心作用是分配内存、初始化核心结构体并设置 RTU 后端属性核心源码如下// 分配modbus上下文结构体内存用于表示整个Modbus总线环境ctx(modbus_t*)malloc(sizeof(modbus_t));if(ctxNULL){returnNULL;}// 初始化上下文公共属性_modbus_init_common(ctx);// 设置RTU模式后端绑定RTU相关的操作函数集ctx-backend_modbus_rtu_backend;// 分配RTU模式专属数据结构体内存用于保存串口相关属性ctx-backend_data(modbus_rtu_t*)malloc(sizeof(modbus_rtu_t));if(ctx-backend_dataNULL){modbus_free(ctx);errnoENOMEM;returnNULL;}ctx_rtu(modbus_rtu_t*)ctx-backend_data;/* Device name and \0 */// 分配内存并保存串口设备名称如Windows的COM

Linux的/dev/ttyUSB0ctx_rtu-device(char*)malloc((strlen(device)

*sizeof(char));if(ctx_rtu-deviceNULL){modbus_free(ctx);errnoENOMEM;returnNULL;}#ifdefined(_WIN

// Windows平台下复制串口设备名称strcpy_s(ctx_rtu-device,strlen(device)1,device);#else// 类Unix平台下复制串口设备名称strcpy(ctx_rtu-device,device);#endif其中核心的结构体赋值逻辑如下用于绑定 RTU 后端并初始化串口相关数据// 绑定Modbus RTU模式的后端操作函数集ctx-backend_modbus_rtu_backend;// 分配RTU专属数据结构体保存串口设备名、波特率等属性ctx-backend_data(modbus_rtu_t*)malloc(sizeof(modbus_rtu_t));modbus_new_rtu函数的核心流程可

总结为分配上下文内存→初始化公共属性→设置 RTU 后端→分配并初始化 RTU 专属串口数据为后续的 Modbus 操作搭建基础环境。

第二步设置从设备地址modbus_set_slave创建完 RTU 上下文后需要调用modbus_set_slave函数指定目标从设备地址确保请求报文能够被正确的从机接收核心代码如下首先是主设备调用的设置逻辑if(use_backendRTU){// 设置目标从设备IDSERVER_ID为预定义的从机地址modbus_set_slave(ctx,SERVER_ID);}modbus_set_slave函数的底层实现如下intmodbus_set_slave(modbus_t*ctx,intslave){// 校验上下文是否有效if(ctxNULL){errnoEINVAL;return-1;}// 调用后端绑定的set_slave函数设置从设备地址returnctx-backend-set_slave(ctx,slave);}该函数的核心作用是将传入的从设备 ID 赋值给modbus_t结构体的slave字段完整的modbus_t结构体如下展示核心字段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;// 后端专属数据如RTU串口属性};这一步的核心目的是为后续报文封装提供从设备地址确保生成的 Modbus 报文包含正确的目标从机标识。

第三步建立串口连接modbus_connect设置完从设备地址后需要调用modbus_connect函数建立与串口的连接完成串口的打开与参数配置核心源码如下intmodbus_connect(modbus_t*ctx){// 校验上下文是否有效if(ctxNULL){errnoEINVAL;return-1;}// 调用后端绑定的connect函数建立串口连接returnctx-backend-connect(ctx);}modbus_connect函数最终会调用_modbus_rtu_connect函数该函数内部包含完整的串口打开与串口参数配置波特率、数据位、停止位、校验位逻辑。

补充对于 Modbus RTU 模式而言主从设备的串口物理连接或虚拟串口连接是通信的基础而modbus_connect函数的核心作用是完成软件层面的串口初始化确保数据能够通过串口正常传输。

需要注意的是主从设备的串口是否成功建立软件连接是后续报文能否正常发送与接收的关键前提。

第四步构造写请求modbus_write_bits完成前期准备工作后进入核心的写请求构造阶段调用modbus_write_bits函数该函数的参数包含了写操作的核心信息起始地址、写入位数、数据缓冲区核心源码与逻辑如下首先是函数的参数定义与局部变量声明intmodbus_write_bits(modbus_t*ctx,intaddr,intnb,constuint8_t*src){intrc;inti;intbyte_count;intreq_length;intbit_check0;intpos0;参数说明addr为写入的线圈起始地址nb为写入的线圈位数src为待写入的数据缓冲区这三个参数明确了写操作的核心信息。

构造基础请求报文通过调用后端的build_request_basis函数构造 Modbus 报文的基础部分源码如下req_lengthctx-backend-build_request_basis(ctx,MODBUS_FC_WRITE_MULTIPLE_COILS,addr,nb,req);该函数最终会调用_modbus_rtu_build_request_basis结合 Modbus 功能码表其核心作用是构造报文的基础字段从设备地址、功能码、起始地址高位、起始地址低位、寄存器数高位、寄存器数低位。

补充该步骤构造的基础报文字段与此前 Modbus 报文解析中的查询报文基础结构完全一致是 Modbus 协议标准化的体现。

计算数据域字节数根据写入的线圈位数计算需要发送的数据域字节数源码如下byte_count(nb/

((nb%

?1:

;该代码的逻辑对应 Modbus 协议中位寄存器的数据字节数计算规则当写入的位数nb为 8 的整数倍时字节数为nb/8当nb不为 8 的整数倍时字节数向上取整nb/8 1多余的位填充为 0不影响有效数据的传输。

第五步发送请求报文send_msg 系列函数构造完成请求报文后通过send_msg函数完成报文的预处理与实际发送整个过程分为两个核心步骤报文预处理计算 CRC 校验与串口发送。

发送报文核心函数send_msgstaticintsend_msg(modbus_t*ctx,uint8_t*msg,intmsg_length){intrc;inti;// 报文发送前预处理核心计算CRC校验码msg_lengthctx-backend-send_msg_pre(msg,msg_length);// 调试模式下打印发送的报文内容十六进制格式if(ctx-debug){for(i0;imsg_length;i)printf([%.2X],msg[i]);printf(\n);}/* In recovery mode, the write command will be issued until to be successful! Disabled by default. */// 错误恢复模式下重复发送直到成功默认禁用do{rcctx-backend-send(ctx,msg,msg_length);if(rc-

{_error_print(ctx,NULL);if(ctx-error_recoveryMODBUS_ERROR_RECOVERY_LINK){

报文预处理计算 CRC 校验send_msg_pre函数的底层实现为_modbus_rtu_send_msg_pre核心作用是计算 Modbus RTU 报文的 CRC16 校验码并追加到报文末尾源码如下staticint_modbus_rtu_send_msg_pre(uint8_t*req,intreq_length){// 计算报文的CRC16校验码uint16_tcrccrc16(req,req_length);/* According to the MODBUS specs (p.

, the low order byte of the CRC comes * first in the RTU message */// 按照Modbus协议规范CRC低位在前高位在后追加到报文末尾req[req_length]crc0x00FF;req[req_length]crc8;// 返回追加校验码后的完整报文长度returnreq_length;}

实际串口发送数据send函数的底层实现为_modbus_rtu_send核心作用是通过串口将完整的 Modbus 报文发送出去支持跨平台Windows / 类 Unix源码如下staticssize_t_modbus_rtu_send(modbus_t*ctx,constuint8_t*req,intreq_length){#ifdefined(_WIN

// Windows平台下通过WriteFile函数写入串口modbus_rtu_t*ctx_rtuctx-backend_data;DWORD n_bytes0;return(WriteFile(ctx_rtu-w_ser.fd,req,req_length,n_bytes,NULL))?(ssize_t)n_bytes:-1;#else#ifHAVE_DECL_TIOCM_RTS// 类Unix平台下支持RTS信号控制modbus_rtu_t*ctx_rtuctx-backend_data;if(ctx_rtu-rts!MODBUS_RTU_RTS_NONE){ssize_tsize;if(ctx-debug){fprintf(stderr,Sending request using RTS signal\n);}ctx_rtu-set_rts(ctx,ctx_rtu-rtsMODBUS_RTU_RTS_UP);usleep(ctx_rtu-rts_delay);sizewrite(ctx-s,req,req_length);usleep(ctx_rtu-onebyte_time*req_lengthctx_rtu-rts_delay);ctx_rtu-set_rts(ctx,ctx_rtu-rts!MODBUS_RTU_RTS_UP);returnsize;}else{#endif// 类Unix平台下直接通过write函数写入串口returnwrite(ctx-s,req,req_length);#ifHAVE_DECL_TIOCM_RTS}#endif#endif}补充若需将 libmodbus 改造成裸机环境使用核心需要修改该函数中的串口发送逻辑替换为对应 MCU 的串口发送 API如 STM32 的HAL_UART_Transmit其余逻辑可保持不变。

RTU 后端核心结构解读整个 Modbus RTU 发送流程的核心支撑是_modbus_rtu_backend结构体该结构体定义了 RTU 模式下所有关键操作的函数指针遵循固定的顺序与格式源码如下constmodbus_backend_t_modbus_rtu_backend{_MODBUS_BACKEND_TYPE_RTU,// 后端类型RTU_MODBUS_RTU_HEADER_LENGTH,// RTU报文头部长度_MODBUS_RTU_CHECKSUM_LENGTH,// RTU报文校验码长度MODBUS_RTU_MAX_ADU_LENGTH,// RTU报文最大长度_modbus_set_slave,// 设置从设备地址_modbus_rtu_build_request_basis,// 构造请求报文基础部分_modbus_rtu_build_response_basis,// 构造响应报文基础部分_modbus_rtu_prepare_response_tid,// 准备响应报文事务ID_modbus_rtu_send_msg_pre,// 发送报文预处理计算CRC_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// 释放RTU相关资源};该结构体是 libmodbus 模块化设计的核心体现所有 RTU 模式的操作都通过该结构体中的函数指针完成这也是 libmodbus 能够支持多后端RTU/TCP的关键所在。

九、

总结libmodbus 发送请求核心流程创建 RTU 上下文→设置从机地址→建立串口连接→构造请求报文→发送报文含 CRC 校验核心支撑是_modbus_rtu_backend结构体封装了 RTU 模式的所有操作函数体现模块化设计思想报文预处理的核心是计算 CRC16 校验码裸机移植需修改串口发送底层 API源码中的字节数计算、报文结构与 Modbus 协议规范完全一致实现了协议的标准化封装。

结尾本次我们深入拆解了 libmodbus 发送请求的完整源码流程清晰看到了 Modbus 协议的标准化封装与模块化设计思想。

吃透这些底层逻辑不仅能熟练使用 libmodbus 进行开发还能根据实际需求进行二次开发与裸机移植。

libmodbus 的源码封装看似复杂实则遵循清晰的协议逻辑与工程规范反复研读能够大幅提升嵌入式工程开发能力。

感谢各位的阅读持续关注本系列笔记一起深耕 Modbus 与开源库开发领域解锁更多实用技能与实战场景

黄色软件视频下载-黄色软件视频下载应用

百度百家号客服电话人工服务

123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123