第一部分:理论模型回顾
在深入代码之前,必须理解 libmodbus 要实现的理论模型。
1. ADU 与 PDU:协议的分层
- PDU (协议数据单元):Modbus 协议的核心,与具体网络无关。包含两部分:功能码 + 数据。功能码指定操作(如读线圈),数据区提供参数(地址、数量)。
- ADU (应用数据单元):实际网络上传输的完整数据包。在 PDU 基础上根据网络类型增加包装。
- 串口 (RTU):PDU 前加从站地址,后加CRC 校验码。
- 以太网 (TCP):PDU 前加MBAP 头(含事务 ID、协议标识、长度等)。
- 软件库的任务:libmodbus 自动完成 PDU 到 ADU 的打包和解包。
2. 事务处理模型:通信的流程
- **客户端/服务器(主站/从站)**模型。
- 主站(客户端):发起请求(含地址、功能码、数据)。
- 从站(服务器):接收请求 -> 执行操作 -> 返回响应(含地址、功能码、结果数据)。
- 异常处理:若出错,从站返回异常响应(功能码最高位置 1,附带错误码)。
- 软件库的任务:封装建立连接、发送请求、接收并解析响应的流程。
第二部分:代码框架与初始化
3. 库的初始化与连接建立
modbus_new_tcp:用于网络通信,参数为 IP 地址和端口号(通常 502)。modbus_new_rtu:用于串口通信,参数为设备名、波特率、校验位、数据位、停止位。- 返回值
ctx:modbus_t类型的指针,即 Modbus 连接句柄。后续所有操作(读、写、设置)均通过此句柄进行。 - 配置参数:
modbus_set_debug(ctx, TRUE):开启调试模式,打印原始字节数据。modbus_set_slave(ctx, 1):仅 RTU 模式有效,设置目标从站地址。
- 建立连接:调用
modbus_connect(ctx)。TCP 连接目标服务器,RTU 打开串口设备。失败返回 -1。
总结:标准编程开头——创建上下文 (modbus_new_*) -> 配置参数 (modbus_set_*) -> 建立连接 (modbus_connect)。
选择后端:libmodbus 支持多种通信方式(RTU 串口、TCP 网络)。需通过分支逻辑选择。
if (use_backend == TCP) {
ctx = modbus_new_tcp("192.168.1.100", 502);
} else {
ctx = modbus_new_rtu("/dev/ttyUSB0", 115200, 'N', 8, 1);
}
第三部分:核心数据结构
4. 核心数据结构:struct _modbus
- 结构体定义:
struct _modbus(别名modbus_t)是连接信息档案袋。 backend指针:指向modbus_backend_t结构体,包含函数指针(如send,receive,build_request_basis)。- 设计模式:策略模式或插件架构。
struct _modbus是通用框架,backend是具体实现插件。modbus_new_rtu创建时,backend被赋值为 RTU 后端函数集。上层代码调用ctx->backend->send(...)即可适配不同底层网络,实现解耦和复用。
结构体成员解析:
struct _modbus {
int slave; // 【从站地址】主站表示目标 ID,从站表示自身 ID。
int s; // 【套接字/文件描述符】OS 级通信句柄。TCP 对应 socket,RTU 对应串口。
int debug; // 【调试开关】由 `modbus_set_debug` 设置。
const modbus_backend_t *backend; // 【后端指针】灵魂所在,指向函数指针集合。
void *backend_data; // 【后端私有数据】给后端函数使用的数据(如 IP、串口参数)。
// ... 超时时间等其他字段
};
第四部分:核心工作流程
5. 主站:发送请求与接收响应的流程
以 modbus_write_bit 为例:
- 构建请求:
ctx->backend->build_request_basis(...)组装 ADU 报文至req缓冲区。 - 发送报文:
send_msg(ctx, req, req_length)调用backend->send发送数据。 - 接收响应:
modbus_receive_msg(ctx, rsp, MSG_CONFIRMATION)等待并接收数据。 - 检查确认:
check_confirmation(ctx, req, rsp, rc)校验地址、功能码、CRC 等。 - 返回结果:返回成功或失败状态。
流程总结:构建 -> 发送 -> 接收 -> 校验 -> 返回。
6. send_msg 函数详解
该函数连接通用逻辑与具体后端实现,处理调试输出和链接错误恢复。
static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length) {
// 预处理(如 RTU 计算 CRC)
msg_length = ctx->backend->send_msg_pre(msg, msg_length);
// 调试输出
if (ctx->debug) {
for (i = 0; i < msg_length; i++) printf("[%.2X]", msg[i]);
}
// 循环发送直到成功(若启用错误恢复)
do {
rc = ctx->backend->send(ctx, msg, msg_length);
} while (rc == -1 && (ctx->error_recovery & MODBUS_ERROR_RECOVERY_LINK));
return rc;
}
7. 从站:请求处理循环
展示 Modbus 从站服务器的主循环:
modbus_receive:读取完整 Modbus 请求 ADU。modbus_reply:核心处理函数。- 解析请求报文(解包 ADU,获取功能码和地址)。
- 根据功能码操作
mb_mapping内存映射表(模拟线圈、寄存器等区域)。 - 构建正常或异常响应报文。
- 调用
backend->send发送响应。
mb_mapping:用户创建并传递的数据结构,包含四大区域在内存中的实际数组。
for (;;) {
do {
rc = modbus_receive(ctx, query); // 接收请求
} while (rc == 0); // 过滤非本从站请求
rc = modbus_reply(ctx, query, rc, mb_mapping); // 处理并回复
if (rc == -1) break; // 出错退出
}
第五部分:应用场景与总结
8. 实际应用场景
- 上位机:运行 libmodbus 编写的主站程序(电脑或工控机)。
- 通信链路:
- RS-485 总线:通过 USB 转 485 或串口卡连接。
- 以太网:支持 Modbus TCP 的设备直接接入网络。
- 从站设备:传感器、仪表、PLC 等,拥有唯一从站地址(1-247)。
- Libmodbus 的角色:上位机中负责与 Modbus 设备对话的通信引擎。
最终总结:Libmodbus 框架精要
- 设计模式:前后端分离的插件式架构。
- 前端 (API):统一友好的接口(如
modbus_read_registers)。 - 后端 (Backend):RTU 和 TCP 实现,提供标准功能函数集。
- 连接上下文 (
modbus_t):粘合剂,内部backend指针决定实际调用的后端函数。
- 前端 (API):统一友好的接口(如
- 工作流程:
- 主站:用户调用 API -> 库构建报文 -> 发送 -> 接收响应 -> 验证 -> 返回。
- 从站:无限循环 -> 接收请求 -> 解析 -> 操作映射表 -> 构建响应 -> 发送。
- 学习意义:
- 理解工业通信字节流层面的工作细节。
- 掌握抽象与接口编程典范(
backend指针设计)。 - 提升调试能力(理解
debug开关与报文打印机制)。


