347 lines
9.7 KiB
Markdown
347 lines
9.7 KiB
Markdown
# API-libmodbus — Modbus 协议栈模块
|
||
|
||
> 版本: v1.0
|
||
> 日期: 2026-07-08
|
||
> 来源: libmodbus v3.1.12(LGPL-2.1-or-later)
|
||
> 状态: 导入完成(开源库,未重构)
|
||
> 修改: 日志输出改为调用 `myLog.h` LOG_I/LOG_E
|
||
|
||
---
|
||
|
||
## 1. 模块概述
|
||
|
||
libmodbus 是工业标准 Modbus 协议的开源实现([libmodbus.org](https://libmodbus.org)),支持:
|
||
|
||
| 功能 | 说明 |
|
||
|------|------|
|
||
| Modbus RTU(串行) | RS-232 / RS-485,CRC 校验 |
|
||
| Modbus TCP(以太网) | TCP/IP,MBAP 报文头 |
|
||
| 主站 / 从站 | 同一套 API 支持主站请求和从站响应 |
|
||
| 全功能码覆盖 | 读线圈/读离散量/读保持寄存器/读输入寄存器/写单/写多/掩码/读写组合 |
|
||
|
||
### 版本信息
|
||
|
||
```
|
||
libmodbus v3.1.12
|
||
Copyright © Stéphane Raimbault
|
||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||
```
|
||
|
||
### 项目适配
|
||
|
||
原 RTU 项目对 libmodbus 做了两处适配:
|
||
1. **日志系统**:源码中 `fprintf(stderr, ...)` → `LOG_I` / `LOG_E`(调用项目 liblog 模块)
|
||
2. **config.h**:配置头文件,启用 `HAVE_DECL_TIOCSRS485` 等宏
|
||
|
||
---
|
||
|
||
## 2. 源文件结构
|
||
|
||
```
|
||
libmodbus/
|
||
├── inc/
|
||
│ ├── modbus.h # 主接口 — API、错误码、功能码、数据结构
|
||
│ ├── modbus-rtu.h # RTU 后端接口 — 创建/串口参数/RTS
|
||
│ ├── modbus-tcp.h # TCP 后端接口 — 创建/listen/accept
|
||
│ ├── modbus-version.h # 版本号宏
|
||
│ ├── modbus-private.h # 内部数据结构(不对外暴露)
|
||
│ ├── modbus-rtu-private.h # RTU 后端内部数据
|
||
│ ├── modbus-tcp-private.h # TCP 后端内部数据
|
||
│ └── config.h # 平台配置宏
|
||
└── src/
|
||
├── modbus.c # 主协议逻辑 — 帧构建/解析/收发
|
||
├── modbus-data.c # 位/寄存器数据转换工具
|
||
├── modbus-rtu.c # RTU 后端 — 串口读写 + CRC
|
||
└── modbus-tcp.c # TCP 后端 — socket 读写 + MBAP
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 核心数据结构
|
||
|
||
### 3.1 modbus_t(不透明上下文句柄)
|
||
|
||
```c
|
||
typedef struct _modbus modbus_t;
|
||
```
|
||
|
||
通过 `modbus_new_rtu()` 或 `modbus_new_tcp()` 创建,所有 API 的第一个参数。
|
||
|
||
### 3.2 数据映射表
|
||
|
||
```c
|
||
typedef struct _modbus_mapping_t {
|
||
int nb_bits; // Coil 数量
|
||
int start_bits; // Coil 起始地址
|
||
int nb_input_bits; // 离散输入数量
|
||
int start_input_bits; // 离散输入起始地址
|
||
int nb_input_registers; // 输入寄存器数量
|
||
int start_input_registers; // 输入寄存器起始地址
|
||
int nb_registers; // 保持寄存器数量
|
||
int start_registers; // 保持寄存器起始地址
|
||
uint8_t *tab_bits; // Coil 数组
|
||
uint8_t *tab_input_bits; // 离散输入数组
|
||
uint16_t *tab_input_registers;// 输入寄存器数组
|
||
uint16_t *tab_registers; // 保持寄存器数组
|
||
} modbus_mapping_t;
|
||
```
|
||
|
||
### 3.3 功能码
|
||
|
||
| 宏 | 值 | 说明 |
|
||
|------|:--:|------|
|
||
| `MODBUS_FC_READ_COILS` | 0x01 | 读线圈 |
|
||
| `MODBUS_FC_READ_DISCRETE_INPUTS` | 0x02 | 读离散输入 |
|
||
| `MODBUS_FC_READ_HOLDING_REGISTERS` | 0x03 | 读保持寄存器 |
|
||
| `MODBUS_FC_READ_INPUT_REGISTERS` | 0x04 | 读输入寄存器 |
|
||
| `MODBUS_FC_WRITE_SINGLE_COIL` | 0x05 | 写单线圈 |
|
||
| `MODBUS_FC_WRITE_SINGLE_REGISTER` | 0x06 | 写单寄存器 |
|
||
| `MODBUS_FC_WRITE_MULTIPLE_COILS` | 0x0F | 写多线圈 |
|
||
| `MODBUS_FC_WRITE_MULTIPLE_REGISTERS` | 0x10 | 写多寄存器 |
|
||
| `MODBUS_FC_MASK_WRITE_REGISTER` | 0x16 | 掩码写寄存器 |
|
||
| `MODBUS_FC_WRITE_AND_READ_REGISTERS` | 0x17 | 读写多寄存器 |
|
||
|
||
### 3.4 协议异常码
|
||
|
||
| 错误宏 | 说明 |
|
||
|------|------|
|
||
| `EMBXILFUN` | 非法功能码 |
|
||
| `EMBXILADD` | 非法数据地址 |
|
||
| `EMBXILVAL` | 非法数据值 |
|
||
| `EMBXSFAIL` | 从站设备故障 |
|
||
| `EMBXACK` | 确认 |
|
||
| `EMBXSBUSY` | 从站繁忙 |
|
||
| `EMBMDATA` | 数据太多(本地错误) |
|
||
| `EMBBADCRC` | CRC 校验失败 |
|
||
|
||
---
|
||
|
||
## 4. 公共 API 接口
|
||
|
||
### 4.1 创建与销毁
|
||
|
||
```c
|
||
/* RTU 模式 */
|
||
modbus_t *modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit);
|
||
|
||
/* TCP 模式 */
|
||
modbus_t *modbus_new_tcp(const char *ip_address, int port);
|
||
|
||
/* TCP PI(协议无关,支持 IPv4/IPv6) */
|
||
modbus_t *modbus_new_tcp_pi(const char *node, const char *service);
|
||
|
||
/* 释放资源 */
|
||
void modbus_free(modbus_t *ctx);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.2 连接管理
|
||
|
||
```c
|
||
/* 建立连接 */
|
||
int modbus_connect(modbus_t *ctx);
|
||
|
||
/* 关闭连接 */
|
||
void modbus_close(modbus_t *ctx);
|
||
|
||
/* 清空缓冲区 */
|
||
int modbus_flush(modbus_t *ctx);
|
||
|
||
/* TCP 服务器端: 监听 */
|
||
int modbus_tcp_listen(modbus_t *ctx, int nb_connection);
|
||
|
||
/* TCP 服务器端: accept */
|
||
int modbus_tcp_accept(modbus_t *ctx, int *s);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.3 参数配置
|
||
|
||
```c
|
||
/* 从站地址 */
|
||
int modbus_set_slave(modbus_t *ctx, int slave);
|
||
int modbus_get_slave(modbus_t *ctx);
|
||
|
||
/* 响应超时(秒 + 微秒) */
|
||
int modbus_set_response_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec);
|
||
int modbus_get_response_timeout(modbus_t *ctx, uint32_t *to_sec, uint32_t *to_usec);
|
||
|
||
/* 字节间超时 */
|
||
int modbus_set_byte_timeout(modbus_t *ctx, uint32_t to_sec, uint32_t to_usec);
|
||
|
||
/* 错误恢复模式 */
|
||
int modbus_set_error_recovery(modbus_t *ctx, modbus_error_recovery_mode mode);
|
||
|
||
/* 调试开关 */
|
||
int modbus_set_debug(modbus_t *ctx, int flag);
|
||
|
||
/* 错误消息 */
|
||
const char *modbus_strerror(int errnum);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.4 数据读写(主站模式)
|
||
|
||
```c
|
||
/* 读线圈 0x01 */
|
||
int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
|
||
|
||
/* 读离散输入 0x02 */
|
||
int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest);
|
||
|
||
/* 读保持寄存器 0x03 */
|
||
int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
|
||
|
||
/* 读输入寄存器 0x04 */
|
||
int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest);
|
||
|
||
/* 写单线圈 0x05 */
|
||
int modbus_write_bit(modbus_t *ctx, int coil_addr, int status);
|
||
|
||
/* 写单寄存器 0x06 */
|
||
int modbus_write_register(modbus_t *ctx, int reg_addr, const uint16_t value);
|
||
|
||
/* 写多线圈 0x0F */
|
||
int modbus_write_bits(modbus_t *ctx, int addr, int nb, const uint8_t *data);
|
||
|
||
/* 写多寄存器 0x10 */
|
||
int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *data);
|
||
|
||
/* 掩码写寄存器 0x16 */
|
||
int modbus_mask_write_register(modbus_t *ctx, int addr, uint16_t and_mask, uint16_t or_mask);
|
||
|
||
/* 读写多寄存器 0x17 */
|
||
int modbus_write_and_read_registers(modbus_t *ctx,
|
||
int write_addr, int write_nb, const uint16_t *src,
|
||
int read_addr, int read_nb, uint16_t *dest);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.5 从站模式
|
||
|
||
```c
|
||
/* 创建数据映射表 */
|
||
modbus_mapping_t *modbus_mapping_new(int nb_bits, int nb_input_bits,
|
||
int nb_registers, int nb_input_registers);
|
||
|
||
/* 创建带起始地址的映射表 */
|
||
modbus_mapping_t *modbus_mapping_new_start_address(
|
||
unsigned int start_bits, unsigned int nb_bits,
|
||
unsigned int start_input_bits, unsigned int nb_input_bits,
|
||
unsigned int start_registers, unsigned int nb_registers,
|
||
unsigned int start_input_registers, unsigned int nb_input_registers);
|
||
|
||
/* 释放映射表 */
|
||
void modbus_mapping_free(modbus_mapping_t *mb_mapping);
|
||
|
||
/* 接收请求(阻塞) */
|
||
int modbus_receive(modbus_t *ctx, uint8_t *req);
|
||
|
||
/* 从映射表自动回复 */
|
||
int modbus_reply(modbus_t *ctx, const uint8_t *req, int req_length, modbus_mapping_t *mb_mapping);
|
||
|
||
/* 手动回复异常 */
|
||
int modbus_reply_exception(modbus_t *ctx, const uint8_t *req, unsigned int exception_code);
|
||
```
|
||
|
||
---
|
||
|
||
### 4.6 原始帧操作
|
||
|
||
```c
|
||
/* 发送原始请求帧 */
|
||
int modbus_send_raw_request(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length);
|
||
|
||
/* 带事务 ID 的原始请求(TCP 多主站) */
|
||
int modbus_send_raw_request_tid(modbus_t *ctx, const uint8_t *raw_req, int raw_req_length, int tid);
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 使用示例
|
||
|
||
### 5.1 Modbus TCP 客户端读取保持寄存器
|
||
|
||
```c
|
||
#include "modbus.h"
|
||
|
||
modbus_t *ctx = modbus_new_tcp("192.168.1.100", 502);
|
||
if (ctx == NULL) { /* error */ }
|
||
|
||
modbus_set_slave(ctx, 1);
|
||
|
||
if (modbus_connect(ctx) == -1) {
|
||
fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
|
||
modbus_free(ctx);
|
||
return;
|
||
}
|
||
|
||
uint16_t tab_reg[32];
|
||
int rc = modbus_read_registers(ctx, 0, 10, tab_reg);
|
||
if (rc == -1) {
|
||
fprintf(stderr, "Read failed: %s\n", modbus_strerror(errno));
|
||
} else {
|
||
for (int i = 0; i < rc; i++) {
|
||
printf("reg[%d]=%d\n", i, tab_reg[i]);
|
||
}
|
||
}
|
||
|
||
modbus_close(ctx);
|
||
modbus_free(ctx);
|
||
```
|
||
|
||
### 5.2 Modbus RTU 从站
|
||
|
||
```c
|
||
#include "modbus.h"
|
||
|
||
modbus_t *ctx = modbus_new_rtu("/dev/ttyS0", 9600, 'N', 8, 1);
|
||
modbus_set_slave(ctx, 1);
|
||
|
||
modbus_mapping_t *mb_mapping = modbus_mapping_new(100, 100, 100, 100);
|
||
if (mb_mapping == NULL) { /* error */ }
|
||
|
||
if (modbus_connect(ctx) == -1) { /* error */ }
|
||
|
||
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
|
||
for (;;) {
|
||
int rc = modbus_receive(ctx, query);
|
||
if (rc > 0) {
|
||
modbus_reply(ctx, query, rc, mb_mapping);
|
||
}
|
||
}
|
||
|
||
modbus_mapping_free(mb_mapping);
|
||
modbus_close(ctx);
|
||
modbus_free(ctx);
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 依赖关系
|
||
|
||
```
|
||
myLog.h → libmodbus
|
||
(libmodbus 源码修改: fprintf → LOG_I/LOG_E)
|
||
```
|
||
|
||
libmodbus 是独立的开源库,仅依赖标准 POSIX(socket / termios / pthread)和项目日志模块。
|
||
|
||
---
|
||
|
||
## 7. 代码审查要点
|
||
|
||
| 项 | 状态 | 说明 |
|
||
|------|:--:|------|
|
||
| 许可证 | LGPL-2.1+ | 兼容项目 |
|
||
| 版本 | v3.1.12 | 稳定发布版 |
|
||
| 源码改动 | 2 处 | 日志宏替换(fprintf → LOG_I/E) |
|
||
| 线程安全 | 否 | 每个 modbus_t 独立,不共享 |
|
||
| 内存管理 | 手动 | modbus_free() / modbus_mapping_free() |
|
||
| RTU CRC | 查表法 | 同 func_cal_crc16 |
|
||
| TCP MBAP | 7 字节头 | 事务 ID + 协议 ID + 长度 + 单元 ID |
|