ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

libmodbus 串口 RTU 通信入门:modbus_new_rtu() 上下文创建全解析

libmodbus 串口 RTU 通信入门:modbus_new_rtu() 上下文创建全解析 通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载modbus_new_rtu() 是 libmodbus 在串行链路上建立 Modbus RTU 通信的入口函数它负责分配并初始化一个modbus_t上下文封装串口设备名、波特率、校验位、数据位与停止位等全部串口参数。本文以 docs/modbus_new_rtu.md 为骨架结合 src/modbus-rtu.c、src/modbus-rtu.h 等仓库源码完整讲解该函数的参数语义、参数校验、返回值与错误处理并深入剖析调用 modbus_connect 后底层串口是如何被配置的以及 RTU 模式下主站master与从站slave角色的正确用法。读完本文你将能独立编写一个可运行的 libmodbus RTU 客户端/服务端程序并理解其底层串口配置与 Modbus RTU 帧格式。函数原型与作用modbus_t *modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit);modbus_new_rtu()在堆上分配并初始化一个modbus_t结构体用于在串行线路上以 RTU 模式进行通信。函数声明位于 src/modbus-rtu.h返回的modbus_t是一个不透明结构包含了建立与其它 Modbus 设备连接所需的全部后端信息对应 docs/index.md 中“Contexts”一节的描述。创建上下文后应用还不会立即占用串口——真正的串口打开与参数下发发生在后续调用modbus_connect(ctx)时。这与 TCP 上下文不同RTU 上下文的device只是字符串记录被拷贝到后端结构体的device字段中见源码 src/modbus-rtu.c。从源码结构看modbus_new_rtu()内部完成的工作包括src/modbus-rtu.c对五个参数逐一做合法性检查非法时设置errno EINVAL并返回 NULL分配modbus_t与modbus_rtu_t两个结构体失败时返回 NULL 并设置errno ENOMEM调用_modbus_init_common()初始化公共字段并将后端指针指向_modbus_rtu_backend该后端虚函数表定义在 src/modbus-rtu.c包含 header_length1、checksum_length2、最大 ADU256 以及连接、发送、接收、CRC 校验、关闭等回调设置 RTU 默认值串口模式默认为 RS232MODBUS_RTU_RS232、RTS 默认不启用MODBUS_RTU_RTS_NONE、confirmation_to_ignore初始化为 FALSE并按波特率与帧格式估算单字节发送时间onebyte_time。参数详解device串口设备名device参数指定操作系统管理的串口设备名称Linux 下常见设备节点如/dev/ttyS0主板原生串口、/dev/ttyUSB0USB 转串口适配器通常为 FTDI/CH340/CP210x 等芯片、/dev/ttyAMA0树莓派等平台macOS 下形如/dev/tty.USA19*、/dev/tty.usbserial-*Windows 下使用COM编号例如COM3注意当 COM 编号大于 9 时必须加上\\.\前缀例如\\\\.\\COM10。这是 Windows 设备命名规范的要求详见 src/modbus-rtu-private.h 中关于设备名的注释。源码对 device 的校验非常严格src/modbus-rtu.cdevice NULL或空字符串都会立即以EINVAL失败防止后续strlen/strcpy越界。baud波特率baud指定通信波特率常见取值包括 9600、19200、57600、115200 等需与对端设备如 PLC、电表、变频器的实际配置一致。源码要求baud 0否则返回EINVALsrc/modbus-rtu.c。值得注意的是Linux 下可用的波特率集合取决于平台头文件_get_termios_speed()src/modbus-rtu.c在B110到B4000000之间按条件编译支持遇到未知波特率时不会报错而是回退到B9600并打印警告WARNING Unknown baud rate %d (B9600 used)。如果系统提供termios2结构HAVE_STRUCT_TERMIOS2则通过BOTHER允许任意自定义波特率例如 14400 这类非标准值也能直接设置src/modbus-rtu.c。parity校验位parity是单个字符取值仅限三种源码在 src/modbus-rtu.c 校验值含义termios 位设置POSIXN无校验none清除PARENB同时清除输入奇偶校验使能INPCKE偶校验even置位PARENB清除PARODD置位INPCKO奇校验odd置位PARENB与PARODD置位INPCK传入其它字符如A会直接导致EINVAL。这背后对应 Modbus 串口常见的 “8N1”“8E1”“8O1” 帧格式约定。data_bit数据位data_bit允许取值 5、6、7、8。源码校验data_bit 5 || data_bit 8即报EINVALsrc/modbus-rtu.c。在 POSIX 侧分别映射为CS5、CS6、CS7、CS8标志Windows 侧映射为DCB.ByteSize的 5/6/7/8src/modbus-rtu.c。Modbus RTU 实际应用中绝大多数场景使用 8 位数据。stop_bit停止位stop_bit仅允许 1 或 2其它值返回EINVALsrc/modbus-rtu.c。POSIX 下通过CSTOPB标志控制stop_bit 1时清除该位为 2 时置位Windows 下对应ONESTOPBIT/TWOSTOPBITS。停止位取值会影响单字节传输时间估算onebyte_time进而影响 RS485 RTS 切换的默认延时见下文“RTU 专属调优 API”。返回值与错误处理modbus_new_rtu()成功时返回指向已初始化modbus_t结构的指针失败时返回 NULL 并设置errnoEINVAL参数非法。触发场景包括device为 NULL 或空字符串、baud 0、data_bit不在 5~8 之间、stop_bit不是 1 或 2、parity不是N/E/O。ENOMEM内存不足。可能是应用自身达到内存上限或整个系统内存耗尽。注意串口不存在、权限不足等运行时错误不会在这里出现——它们发生在后续的modbus_connect()阶段。创建阶段只做“参数合法性 内存分配”两类检查这一设计让调用方可以在分配失败时立即释放资源、安全退出。仓库单元测试对上述校验做了直接验证tests/unit-test-client.c/* Test init functions */ ctx modbus_new_rtu(NULL, 1, A, 0, 0); ASSERT_TRUE(ctx NULL errno EINVAL, ); ctx modbus_new_rtu(/dev/dummy, 0, A, 0, 0); ASSERT_TRUE(ctx NULL errno EINVAL, );第一行以 NULL 设备名、非N/E/O校验位触发 EINVAL第二行以波特率 0 触发 EINVAL。错误信息可以通过 modbus_strerror 转换为可读字符串例如modbus_strerror(errno)。RTU 模式下你的角色从站slave还是主站master在 Modbus RTU 术语中server服务端被称为 slave从站client客户端被称为 master主站通信总是由主站发起。用modbus_new_rtu()创建上下文后你的程序可以担任任意一种角色从站server/slave程序对外暴露数据处理并应答来自一个或多个主站的请求。你需要通过 modbus_set_slave 定义本服务的从站 ID该 ID 供主站用来寻址你的程序。主站client/master程序向从站发送读写请求。在发出任何请求之前必须先用 modbus_set_slave 设置远端设备的从站 ID。从站 ID 不是读写函数的参数因为实践中通常只与一台从站通信设置一次即可若需要轮询多台从站可以在每次请求前切换 ID。RTU 从站 ID 的合法范围由底层_modbus_set_slave()强制约束src/modbus-rtu.c默认允许 0~247若启用了MODBUS_QUIRK_MAX_SLAVE怪癖则放宽到 255其中 0 是广播地址MODBUS_BROADCAST_ADDRESS。越界时返回 -1 并设置EINVAL。在 TCP 模式下从站 ID 通常不使用这也保证了 API 在 RTU/TCP 之间的通用性——详见 docs/modbus_set_slave.md。上下文创建之后modbus_connect 底层做了什么创建 RTU 上下文后调用 modbus_connect 建立连接成功返回 0失败返回 -1 并设置 errno。对于 RTU 后端这一步执行_modbus_rtu_connect()其行为因平台而异POSIXLinux/macOS/FreeBSDsrc/modbus-rtu.c以O_RDWR | O_NOCTTY | O_NONBLOCK外加可用时的O_CLOEXEC打开设备节点——O_NOCTTY防止串口成为进程的控制终端O_NONBLOCK让后续读写配合 select 超时机制工作保存当前termios/termios2设置到old_tios以便modbus_close()时恢复设置波特率termios2 走BOTHER自定义速率否则查表映射、数据位CS5~CS8、停止位CSTOPB、校验位PARENB/PARODD/INPCK进入原始模式清除ICANON | ECHO | ECHOE | ISIG清除输出后处理OPOST禁用软件流控IXON|IXOFF|IXANYVMIN 0、VTIME 0配合 NONBLOCK打开失败或配置失败时返回 -1并关闭已打开的文件描述符。Windowssrc/modbus-rtu.c通过CreateFileA打开设备句柄读取当前DCB再写入波特率、字节数ByteSize 5~8、停止位ONESTOPBIT/TWOSTOPBITS、校验NOPARITY/EVENPARITY/ODDPARITY保持默认硬件握手、禁用软件握手、强制二进制模式最后SetCommState生效。连接建立后modbus_close 会恢复保存的串口设置并关闭句柄POSIX 走tcsetattr(TCSANOW)还原Windows 走SetCommState(old_dcb)因此无需担心串口参数被“污染”。完整示例读取从站 ID 10 的寄存器以下示例完整继承自 docs/modbus_new_rtu.md 并补充了错误分支程序通过 USB 转串口设备打开 115200、8N1 的 RTU 链路所有后续寄存器读写请求都会发送到串口线路上对链路上所有设备可见根据 Modbus 协议只有从站 ID 为 10 的设备会处理并应答这些请求。#include stdio.h #include stdint.h #include modbus.h int main(void) { const int REMOTE_ID 10; modbus_t *ctx; uint16_t tab_reg[10]; /* 创建 RTU 上下文/dev/ttyUSB0, 115200 baud, 无校验, 8 数据位, 1 停止位 */ ctx modbus_new_rtu(/dev/ttyUSB0, 115200, N, 8, 1); if (ctx NULL) { fprintf(stderr, Unable to create the libmodbus context\n); return -1; } /* 建立连接此刻才真正打开串口并下发 termios 参数 */ if (modbus_connect(ctx) -1) { fprintf(stderr, Connection failed: %s\n, modbus_strerror(errno)); modbus_free(ctx); return -1; } /* 设置远端从站 ID主站角色必须在发起请求之前完成 */ modbus_set_slave(ctx, REMOTE_ID); /* 从从站 ID 10 的地址 0 开始读取 2 个保持寄存器 */ modbus_read_registers(ctx, 0, 2, tab_reg); modbus_close(ctx); modbus_free(ctx); return 0; }代码中的串口参数组合115200, N, 8, 1即工业界最常见的115200-8N1。仓库内的测试程序采用了相同的调用模式例如 tests/unit-test-client.c、tests/bandwidth-client.c、tests/bandwidth-server-one.c、tests/random-test-client.c 均以modbus_new_rtu(设备名, 波特率, N, 8, 1)初始化上下文其中带宽测试与随机测试还提供了 RTU 主站/从站的完整配对示例可供参考。RTU 帧格式与 CRC 校验原理创建 RTU 上下文时后端虚函数表src/modbus-rtu.c就固定了 RTU 的报文结构特征ADU 上限MODBUS_RTU_MAX_ADU_LENGTH定义为 256 字节即 Modbus 应用数据单元 253 字节 从站地址 1 字节 CRC 校验 2 字节见 src/modbus-rtu.h帧头请求预设长度为 6 字节从站 ID 1 字节 功能码 1 字节 地址高/低字节 数量高/低字节响应预设长度 2 字节校验和 2 字节src/modbus-rtu-private.h无事务 ID_modbus_rtu_get_response_tid()直接返回 0src/modbus-rtu.c这是 RTU 与 TCP 后端在报文头上的关键差异CRC16 校验_modbus_rtu_send_msg_pre()对报文计算 CRC-16查表实现见crc16()与table_crc_hi/lo并按 Modbus 规范低字节在前追加 2 字节src/modbus-rtu.c接收侧_modbus_rtu_check_integrity()重新计算并与收到的 CRC 比对不一致时设置EMBBADCRC若启用了协议级错误恢复还会冲刷接收缓冲区src/modbus-rtu.c从站过滤接收校验时仅接受目标为自己的帧其它从站 ID 的请求被丢弃广播地址除外这正是一个串口链路上挂接多台设备时的寻址基础src/modbus-rtu.c。另外docs/index.md 特别提醒libmodbus 的 RTU 实现并非基于 Modbus 原规范的时间间隔来界定帧而是“尽快发送全部字节收到全部期望字节即认为一帧完整”。这种实现通信极快但要求主站的响应超时设置得大于从站的响应超时否则某台从站无响应时其余从站可能因帧未闭合而忽略主站请求。RTU 上下文创建后可用的调优 APImodbus_new_rtu()创建的上下文默认采用 RS232 模式、不启用 RTS 控制。在 RS485 半双工总线等场景下可通过 docs/index.md 列出的系列函数进一步调优函数作用相关常量/默认值modbus_rtu_set_serial_mode / modbus_rtu_get_serial_mode在 RS232/RS485 之间切换Linux 下基于TIOCSRS485ioctl默认MODBUS_RTU_RS232MODBUS_RTU_RS232 0MODBUS_RTU_RS485 1src/modbus-rtu.hmodbus_rtu_set_rts / modbus_rtu_get_rts启用 RTS 引脚控制常用于 RS485 收发方向切换默认MODBUS_RTU_RTS_NONEMODBUS_RTU_RTS_NONE 0MODBUS_RTU_RTS_UP 1MODBUS_RTU_RTS_DOWN 2src/modbus-rtu.hmodbus_rtu_set_custom_rts注册自定义 RTS 切换回调如 GPIO 控制的转换器默认回调为_modbus_rtu_ioctl_rts基于TIOCM_RTSmodbus_rtu_set_rts_delay / modbus_rtu_get_rts_delay设置 RTS 切换前后的延时微秒默认等于单字节发送时间由modbus_new_rtu()在创建时按1000000 * (1 data_bit (parityN ? 0 : 1) stop_bit) / baud估算src/modbus-rtu.c这些函数均在modbus_new_rtu()之后、modbus_connect()前后调用且仅对 RTU 后端生效对非 RTU 上下文会返回 -1 并设置EINVAL。参见modbus_connect建立 Modbus 连接modbus_close关闭 Modbus 连接并恢复串口设置modbus_set_slave设置/切换从站 IDmodbus_read_registers主站读取保持寄存器modbus_strerror将错误码转换为可读信息modbus_free释放上下文资源modbus_new_tcpTCP 后端的上下文创建函数便于对比两种传输方式docs/index.mdlibmodbus 整体概念、RTU 上下文及全部 API 索引赞分享通信嵌入式物联网【免费下载链接】libmodbusA Modbus library for Linux, Mac OS, FreeBSD and Windows项目地址https://gitcode.com/gh_mirrors/li/libmodbus点击查看免费下载相关推荐libmodbus RTU模式深度解析串行通信的终极解决方案libmodbus RTU模式深度解析串行通信的终极解决方案 想要在工业自动化、物联网设备或嵌入式系统中实现稳定可靠的串行通信吗libmodbus R通信嵌入式物联网如何快速上手bert-base-german-uncased完整的NPU部署指南如何快速上手bert base german uncased完整的NPU部署指南 想要在昇腾NPU上快速部署德语BERT模型吗bert base germa上一篇终极Python-CAN指南如何用Python轻松实现CAN总线通信下一篇BlockNote 表格导出 Markdown 实战深入解析 advancedExample 快照与合并单元格序列化原理创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表