RTU_ALL_AI/docs/protocol/libmodbus/API-libmodbus.md

347 lines
9.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# API-libmodbus — Modbus 协议栈模块
> 版本: v1.0
> 日期: 2026-07-08
> 来源: libmodbus v3.1.12LGPL-2.1-or-later
> 状态: 导入完成(开源库,未重构)
> 修改: 日志输出改为调用 `myLog.h` LOG_I/LOG_E
---
## 1. 模块概述
libmodbus 是工业标准 Modbus 协议的开源实现([libmodbus.org](https://libmodbus.org)),支持:
| 功能 | 说明 |
|------|------|
| Modbus RTU串行 | RS-232 / RS-485CRC 校验 |
| Modbus TCP以太网 | TCP/IPMBAP 报文头 |
| 主站 / 从站 | 同一套 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 是独立的开源库,仅依赖标准 POSIXsocket / 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 |