Skip to content

youzuwei/ymodbus

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ymodbus 协议栈使用说明

1. 简介与特性

ymodbus 是平台可移植的 Modbus 协议栈,支持 Modbus RTUModbus TCP 两种传输方式,同时提供 主站(Master)从站(Slave) 角色。协议栈通过收发回调与底层串口/网络解耦,便于在 RS485、以太网等场景下集成。端口层(ports)ymodbus_port.h 中抽象互斥锁、链表与堆,支持 RT-ThreadFreeRTOSLinux 以及未定义平台时的裸机风格;日志通过 ymodbus_log.h 按平台选择 RT-Thread ulog、或类 RT-Thread 的 printf 格式(见 3.2 节)。

1.1 主要特性

  • 协议:Modbus RTU / Modbus TCP(ADU 格式符合标准,RTU 最小 4 字节、最大 256 字节;TCP 最小 8 字节、最大 260 字节)
  • 角色:主站、从站(通过 YMODBUS_USING_MASTER / YMODBUS_USING_SLAVE 编译裁剪)
  • 功能码:0x01~0x06、0x0F、0x10、0x11、0x16、0x17(读线圈/离散输入/保持寄存器/输入寄存器,单/多写,掩码写,写读寄存器,报告从站 ID)
  • 资源:支持静态分配(无堆)与动态分配(需 YMODBUS_USING_HEAP,提供 ymodbus_create / ymodbus_slave_create / ymodbus_registers_create 等)
  • 线程安全:对 struct ymodbus 的访问由端口层提供的 ymodbus_mutex 保护
  • 从站:支持多从站挂在同一 struct ymodbus 下,寄存器区通过 ymodbus_registers 做多段映射(线圈/离散输入/保持/输入寄存器)

1.2 头文件与依赖

  • 主站#include <ymodbus_master.h>(已包含 ymodbus_core)
  • 从站#include <ymodbus_slave.h>#include <ymodbus_registers.h>
  • 协议常量ymodbus_rtu.hymodbus_tcp.h(ADU 长度等)
  • 配置ymodbus_cfg.h(平台宏、YMODBUS_USING_SLAVE / YMODBUS_USING_MASTER
  • 端口ymodbus_port.h(由 ymodbus_core.h 间接包含,提供互斥锁、链表、可选堆接口)
  • 日志ymodbus_log.h(按平台使用 RT-Thread rtdbg/ulog 或 printf 风格,见 3.2 节)
  • 工具ymodbus_utils.h(CRC、非 RT-Thread 下的 slist 实现)
  • 依赖:根据所选平台(RT-Thread:rt_mutex、rt_slist、可选 rtdbg;FreeRTOS/Linux:见 ports 与 log 实现)

2. 目录结构

ymodbus/
├── inc/
│   ├── ymodbus_core.h      # 核心类型、设备结构、init/detach、收发注册、PDU 结构
│   ├── ymodbus_cfg.h       # 平台与功能开关(RTTHREAD/FREERTOS/LINUX、YMODBUS_USING_SLAVE/MASTER)
│   ├── ymodbus_master.h    # 主站:序列化请求、解析响应、读写 API
│   ├── ymodbus_slave.h     # 从站:slave 结构、request_handle、init/attach
│   ├── ymodbus_registers.h # 从站寄存器区:add/create、remove/delete、cleanup
│   ├── ymodbus_rtu.h       # RTU 常量(ADU 长度等)
│   ├── ymodbus_tcp.h       # TCP 常量
│   ├── ymodbus_log.h       # 平台相关日志(RT-Thread ulog / printf 风格)
│   └── ymodbus_utils.h     # CRC、非 RT-Thread 下的 slist
├── ports/
│   └── ymodbus_port.h      # 端口层:互斥锁、链表、堆(RTTHREAD/FREERTOS/LINUX/else)
├── src/
│   ├── ymodbus_core.c      # 设备管理、RTU/TCP 变体、CRC/头部、request_pdu_length
│   ├── ymodbus_master.c    # 主站请求组帧与响应解析、一站式读写
│   ├── ymodbus_slave.c     # 从站请求解析与寄存器调度
│   ├── ymodbus_registers.c # 寄存器区读写、默认 read/write 实现
│   └── ymodbus_utils.c     # CRC 计算
├── docs/
│   └── README.md           # 本文档
├── samples/
│   ├── main_slave.c        # 从站演示(本地注入 RTU 请求并回包)
│   ├── main_master.c       # 主站演示(主站-从站内存环回仿真)
│   └── Makefile            # Linux/WSL 下样例构建(支持 EXAMPLE 参数)
├── SConscript              # 构建:YMODBUS_USING / YMODBUS_USING_MASTER / YMODBUS_USING_SLAVE
└── kconfig                 # 可选 Kconfig 入口(本工程在 board/Kconfig 中配置)

3. 配置与移植

3.1 功能开关(ymodbus_cfg.h)

在包含协议栈头文件之前,需定义平台宏功能宏(可通过编译选项或 ymodbus_cfg.h 统一配置):

  • 平台(任选其一):
    • RTTHREAD — RT-Thread(mutex/slist/堆由 RT 提供,日志走 rtdbg/ulog)
    • FREERTOS — FreeRTOS(mutex/slist/堆见 port,日志为 printf 风格)
    • LINUX — Linux 用户态(pthread/stdlib 堆,日志为 printf 风格)
    • 均未定义 — 裸机风格(无互斥、无堆,日志为 printf 风格)
  • 从站YMODBUS_USING_SLAVE — 启用从站及 ymodbus_slave_*ymodbus_registers_*
  • 主站YMODBUS_USING_MASTER — 启用主站及 ymodbus_master_*

动态分配(堆)由端口层根据平台自动定义:RT-Thread 下由 RT_USING_HEAP 决定是否定义 YMODBUS_USING_HEAP;FreeRTOS 下由 configSUPPORT_DYNAMIC_ALLOCATION 决定;Linux 下默认 YMODBUS_USING_HEAP;裸机/未定义平台不提供堆。

3.2 日志(ymodbus_log.h)

协议栈内部使用统一宏 LOG_E / LOG_W / LOG_I / LOG_D。各源文件在包含 ymodbus_log.h 前需定义 YMODBUS_LOG_TAG(如 "ymodbus.core""ymodbus.master""ymodbus.slave")。按平台行为如下:

平台宏 日志实现
RTTHREAD 使用 rtdbg.h(ulog),可配合 DBG_TAG/DBG_LVL
FREERTOS / LINUX / 其他 printf 风格,格式为 [E/W/I/D][tag] msg,与 RT-Thread 输出风格一致

在非 RT-Thread 平台需自定义输出(如重定向到串口或 syslog)时,可在包含 ymodbus_log.h 前定义:

#define YMODBUS_LOG_PRINTF(fmt, ...)  my_printf(fmt, ##__VA_ARGS__)

3.3 本工程构建配置(Kconfig)

board/Kconfig 中:

  • YMODBUS_USING:总开关,启用 ymodbus 组件
  • YMODBUS_USING_MASTER:启用主站
  • YMODBUS_USING_SLAVE:启用从站

SConscript 根据上述依赖编译 ymodbus_core.cymodbus_utils.c,以及按需的 ymodbus_master.cymodbus_slave.cymodbus_registers.c。头文件路径包含 incports

3.4 协议栈架构简述

  • 核心(ymodbus_core)struct ymodbus 表示一条 Modbus 通道(RTU 或 TCP),持有缓冲区、收发回调、互斥锁;通过 ymodbus_variable 区分 RTU(CRC、1 字节从站地址)与 TCP(MBAP 头、事务 ID、无 CRC)。设备创建/初始化后注册到全局链表,供按名查找。
  • 主站(ymodbus_master):根据请求 PDU 组帧为 ADU(加头部、CRC),经 send_cb 发送;再经 rcv_cb 收响应,校验 CRC、解析响应 PDU 或异常码,写回 resp_buf。一站式 API 内部完成「序列化请求 → 发送 → 接收 → 解析」。
  • 从站(ymodbus_slave):同一 struct ymodbus 可挂多个 ymodbus_slave(不同从站地址)。收到一帧后由应用将长度传入 ymodbus_slave_request_handle;内部校验 CRC、按地址找从站、按功能码与地址区间查找寄存器区(ymodbus_registers),执行默认或自定义 read/write,组响应或异常帧并经 send_cb 回包。
  • 寄存器(ymodbus_registers):四类区(线圈、离散输入、保持、输入寄存器),每区可多段;每段有起始/结束地址、数据指针及可选的 read/write 回调,跨段读写由协议栈按地址拼接。

4. 主站(Master)使用说明

4.1 初始化与收发回调(applications/main.c 实际用法)

主站使用前需创建一个 struct ymodbus 实例,并为其提供接收发送回调,使协议栈能从“当前通道”收发包。本工程采用 动态创建 方式(需 YMODBUS_USING_HEAP),也演示了和 RS485 的配合。

#include <ymodbus_master.h>
#include <ymodbus_rtu.h>

struct rs485 *mb_rs485;
struct ymodbus *mb_rtu;

/* 接收:把数据读入 buf,返回读到的字节数,失败返回负值 */
static int mb_rcv_cb(void *data, void *buf, uint16_t bufsz)
{
    return rs485_wait_receive(mb_rs485, buf, bufsz, 5000);
}

/* 发送:把 req_buf 发出去,返回发送字节数,失败返回负值 */
static int mb_send_cb(void *data, void *buf, uint16_t bufsz)
{
    /* 可选:发前清空接收缓冲,避免收到自己发的内容 */
    char cbuf[64];
    rs485_wait_receive(mb_rs485, cbuf, sizeof(cbuf), 10);
    return rs485_transmit(mb_rs485, buf, bufsz);
}

int main(void)
{
    int ret;

    /* 创建并连接 RS485 口(本工程使用 uart5 + 方向控制脚) */
    mb_rs485 = rs485_create("rs485.5", "uart5", GET_PIN(F, 8), PIN_HIGH);
    ret = rs485_connect(mb_rs485);

    /* 创建 Modbus RTU 主站实例(内部自动分配缓冲区,需 YMODBUS_USING_HEAP) */
    mb_rtu = ymodbus_create("mbmaster",
                            YMODBUS_TYPE_RTU,
                            YMODBUS_RTU_ADU_MAX_LENGTH,
                            mb_rcv_cb,
                            mb_send_cb,
                            NULL);
    if (!mb_rtu)
    {
        /* 错误处理 */
    }
}

若希望完全静态分配(无堆),可使用 ymodbus_init,缓冲区由调用方提供:

struct ymodbus mb_rtu;
uint8_t mb_rtu_buf[YMODBUS_RTU_ADU_MAX_LENGTH];

ymodbus_init("mb_rtu", &mb_rtu, YMODBUS_TYPE_RTU,
             mb_rtu_buf, sizeof(mb_rtu_buf),
             mb_rcv_cb, mb_send_cb, NULL);

4.2 一站式读写 API(阻塞)

主站提供“发请求 + 收响应”的一站式接口,内部先 ymodbus_master_serialize_requestymodbus_master_response_handle超时与阻塞由 rcv_cb 实现决定(例如上面在 rs485_wait_receive 里等待 5000ms)。

uint8_t  slave_addr = 1;
uint16_t regs[10];
int      ret;

/* 读输入寄存器 FC 0x04,本工程中用于读取告警条数等 */
ret = ymodbus_master_read_input_registers(slave_addr, mb_rtu, 0, 1, regs);

/* 继续读取告警详细信息(示例) */
ret = ymodbus_master_read_input_registers(slave_addr, mb_rtu, 1, 3, regs);

/* 写多个保持寄存器 FC 0x10,本工程用于清除告警信息 */
ret = ymodbus_master_write_multiple_registers(slave_addr, mb_rtu, 0, 2, regs);

返回值:0 表示成功;负数为错误码(如 -EINVAL-ENODATA-EBADMSG 等);若返回值大于 0,表示从站返回了 Modbus 异常码(如 0x01 非法功能、0x02 非法数据地址等,见 enum ymodbus_exception)。

其他主站 API:读线圈/离散输入、写单/多线圈、报告从站 ID、掩码写寄存器、写读寄存器等,见 ymodbus_master.h

4.3 两步 API(自定义超时/非阻塞)

若需自己控制“发完请求后”的等待时间或非阻塞轮询,可拆成两步:

  1. 发请求ymodbus_master_serialize_request(slave_addr, mb, &pdu, &req_len);
    • mb->buf 中组好 ADU 并调用 send_cb 发送。
  2. 收响应:在合适时机调用
    ymodbus_master_response_handle(mb, &pdu, resp_len);
    • 若由协议栈内部收包,可传 resp_len=0,内部通过 rcv_cb 收数据;否则由调用方先收好再传入 resp_len

4.4 Linux/WSL 主站样例(samples/main_master.c)

仓库 samples/main_master.c 提供了纯软件仿真主站演示:主站通过回调把请求送到本地从站处理,再把响应返回给主站,便于无串口硬件时调试主站流程。

构建与运行:

cd samples
make EXAMPLE=master
./ymodbus_master_sample

5. 从站(Slave)使用说明

从站需在打开 Modbus 设备(如 RS485)并收到一帧数据后,将数据放入 mb->buf,再调用 ymodbus_slave_request_handle。协议栈会完成 CRC 校验、解析 PDU、按地址查找从站、调度寄存器读写、组响应或异常帧,最后通过 send_cb 回包。

5.1 初始化 Modbus 与从站(applications/main.c 实际用法)

#include <ymodbus_slave.h>
#include <ymodbus_registers.h>

struct ymodbus      *mb_rtu;
struct ymodbus_slave *mb_slave;

/* 复用主站的 mb_rtu:即一个 ymodbus 实例同时挂主站和本地从站 */
mb_rtu   = ymodbus_create("mbmaster", YMODBUS_TYPE_RTU,
                          YMODBUS_RTU_ADU_MAX_LENGTH,
                          mb_rcv_cb, mb_send_cb, NULL);
mb_slave = ymodbus_slave_create("slave1", mb_rtu, 1);  /* 需 YMODBUS_USING_HEAP */
if (!mb_slave)
{
    /* 错误处理,例如 ymodbus_detach(mb_rtu); */
}

也可使用静态从站:ymodbus_slave_init(&slave, "slave1", mb_rtu, 1),再 ymodbus_slave_attach_mb 等。

5.2 寄存器映射(线圈/离散输入/保持/输入寄存器)

从站通过“寄存器区”暴露数据。每个区是一段地址区间,对应一块内存(或自定义 read/write 回调)。

  • 线圈(Coils):可读可写,位数据。
  • 离散输入(Discrete Inputs):只读,位数据。
  • 保持寄存器(Holding Registers):可读可写,16 位。
  • 输入寄存器(Input Registers):只读,16 位。

方式一:动态创建(需 YMODBUS_USING_HEAP)

/* 保持寄存器:0~39 分成 4 段,每段 10 个寄存器 */
static uint16_t holding_regs1[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
/* ... holding_regs2 ~ holding_regs4 ... */

/* 输入寄存器、线圈、离散输入示例数据 ... */

ymodbus_registers_create(mb_slave, 0, 10, holding_regs1, YMODBUS_REGISTERS_TYPE_HOLDING_REGISTERS);
/* ... 其他 ymodbus_registers_create ... */

/* 删除某段寄存器(按 regs 指针与 type 匹配删除) */
ymodbus_registers_delete(mb_slave, holding_regs1, YMODBUS_REGISTERS_TYPE_HOLDING_REGISTERS);

方式二:静态添加

uint16_t holding_buf[32];
struct ymodbus_registers reg_holding = {
    .start_addr = 0,
    .end_addr   = 31,
    .regs       = holding_buf,
    .read       = NULL,  /* 使用协议栈默认 read_registers */
    .write      = NULL,  /* 使用协议栈默认 write_registers */
};

ymodbus_registers_add(mb_slave, &reg_holding, YMODBUS_REGISTERS_TYPE_HOLDING_REGISTERS);

可多次 add/create,实现多段地址;协议栈会按地址区间自动拼接读/写。自定义 read/write 时,在 struct ymodbus_registers 中指定自己的函数指针即可。移除/清理:ymodbus_registers_removeymodbus_registers_cleanup

5.3 处理请求

从站侧,当从 RS485 等收到一帧并写入 mb->buf 后,必须将本帧长度传入 req_len,再调用:

uint16_t resp_len;
int ret = ymodbus_slave_request_handle(mb_rtu, req_len, &resp_len);
  • req_len:本次收到的 ADU 字节数(协议栈不会在从站内部用 rcv_cb 收数,需由应用先读好再传)。
  • ret == 0 表示成功处理并已通过 send_cb 回包;负数为系统错误;从站内部异常会组装成 Modbus 异常响应并发送。

5.4 Linux/WSL 从站样例(samples/main_slave.c)

仓库 samples/main_slave.c 提供了从站演示:程序内构造一条 RTU 读保持寄存器请求,注入 mb->buf 后调用 ymodbus_slave_request_handle,并打印响应帧。

构建与运行:

cd samples
make EXAMPLE=slave
./ymodbus_slave_sample

6. samples 目录构建说明(Makefile 参数)

samples/Makefile 通过 EXAMPLE 参数选择编译目标:

  • EXAMPLE=slave:构建 ymodbus_slave_sample(对应 main_slave.c
  • EXAMPLE=master:构建 ymodbus_master_sample(对应 main_master.c

常用命令:

cd samples
make EXAMPLE=slave
make EXAMPLE=master
make debug EXAMPLE=slave
make debug EXAMPLE=master
make help

debug 目标会使用 -O0 -g 重新构建,便于 GDB/VSCode 断点调试。


7. 与 RS485 的集成示例(本工程用法)

本工程中,主站通过 RS485 访问现场 Modbus 设备,同时在本机上挂了一个测试从站 mb_slave。下面示例与 applications/main.c 一致。

7.1 全局与回调

struct rs485  *mb_rs485;
struct ymodbus *mb_rtu;

static int mb_rcv_cb(void *data, void *buf, uint16_t bufsz)
{
    return rs485_wait_receive(mb_rs485, buf, bufsz, 5000);
}

static int mb_send_cb(void *data, void *buf, uint16_t bufsz)
{
    char cbuf[64];
    rs485_wait_receive(mb_rs485, cbuf, sizeof(cbuf), 10);
    return rs485_transmit(mb_rs485, buf, bufsz);
}

7.2 初始化

mb_rs485 = rs485_create("rs485.5", "uart5", GET_PIN(F, 8), PIN_HIGH);
rs485_connect(mb_rs485);

mb_rtu   = ymodbus_create("mbmaster", YMODBUS_TYPE_RTU,
                          YMODBUS_RTU_ADU_MAX_LENGTH,
                          mb_rcv_cb, mb_send_cb, NULL);
mb_slave = ymodbus_slave_create("slave1", mb_rtu, 1);

/* 通过 ymodbus_registers_create 给 mb_slave 绑定 4 类寄存器区,见 5.2 节 */

7.3 轮询读从站

uint8_t slave_addr = 1;

while (1)
{
    alarm_process(slave_addr, phone_numbers);
    rt_thread_mdelay(1000);
    if (slave_addr < slave_addr_max)
        slave_addr++;
    else
        slave_addr = 1;
}

即本工程“主站 + 本地从站 + RS485”的完整链路:应用调用主站 API → 协议栈组帧并 send_cb → RS485 发送 → 现场从站应答 → rcv_cb 收数据 → 协议栈解析并写回应用提供的 resp_buf;本机 mb_slave 也可被外部主站访问,用于联调和自测。


8. Modbus 异常码(主站返回值 > 0)

主站一站式 API 返回值大于 0 时,表示从站返回的 Modbus 异常码,对应 enum ymodbus_exception

宏名 含义
0x01 YMODBUS_EXCEPTION_ILLEGAL_FUNCTION 非法功能码
0x02 YMODBUS_EXCEPTION_ILLEGAL_DATA_ADDRESS 非法数据地址
0x03 YMODBUS_EXCEPTION_ILLEGAL_DATA_VALUE 非法数据值
0x04 YMODBUS_EXCEPTION_SLAVE_OR_SERVER_FAILURE 从站设备故障
0x0A YMODBUS_EXCEPTION_GATEWAY_PATH 网关路径不可用
0x0B YMODBUS_EXCEPTION_GATEWAY_TARGET 网关目标无响应
0xFF YMODBUS_EXCEPTION_UNKNOWN 未知异常

9. 优缺点概览

9.1 优点

  • 平台可移植:端口层(ymodbus_port.h)支持 RT-Thread、FreeRTOS、Linux 及裸机风格;日志层(ymodbus_log.h)在 RT-Thread 下用 rtdbg/ulog,其他平台用类 RT-Thread 的 printf 格式,可自定义 YMODBUS_LOG_PRINTF
  • 主从 + 多从站 + 寄存器抽象完善:从站支持多 slave、多段寄存器映射,跨段读写由协议栈处理,业务只需提供缓冲区或自定义 read/write。
  • 支持纯静态资源:无堆时可运行主站/从站(ymodbus_initymodbus_slave_initymodbus_registers_add),适合 RAM 紧张的 MCU。
  • 协议覆盖常用功能码:满足绝大多数工业设备与网关需求;参数与长度校验严格,符合标准。
  • 收发与传输解耦:通过 rcv_cb/send_cb 可接 RS485、TCP、其他串口等,便于复用。
  • 错误码统一:使用 errno 风格负值及 Modbus 异常码(主站返回值 > 0),便于上层处理。

9.2 不足与注意点

  • 依赖所选平台:需正确配置 ymodbus_cfg.hymodbus_port.h,移植到新 OS 或裸机需实现端口宏(mutex、slist、可选堆)。
  • 生态与文档:相比 libmodbus/freemodbus 等,第三方教程较少,需结合源码与本文档使用。
  • 超时与重试:协议栈不内置超时与重试,需在 rcv_cb 或两步 API 的应用层实现。
  • 扩展功能码:标准功能码以外的自定义功能码需在协议栈内扩展 switch 或预留 hook 机制。

10. 参考

  • 头文件:components/ymodbus/inc/components/ymodbus/ports/
  • 应用示例:本工程 applications/main.c 中的 Modbus 主站 + RS485 调用。
  • Modbus 规范:Modbus Application Protocol V1.1b3、Modbus over serial line V1.02。

11. 文档变更记录

日期 说明
2026-03-09 同步 samples 用法:新增 main_slave.c/main_master.c 说明,补充 make EXAMPLE=slave/masterdebug 参数化构建方式。
2025-02-10 与协议栈代码同步:补充平台(RTTHREAD/FREERTOS/LINUX)、目录增加 ymodbus_log.h、新增 3.2 日志与 3.4 协议栈架构;修正端口与优缺点描述。

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors