1、Libmodbus简介

        在单片机上常用的Modbus库有FreeModbus和Libmodbus,这里介绍Libmodbus。

(1)Libmodbus是一个免费的跨平台支持RTU和TCP的Modbus库,遵循LGPL V2.1+协议。

(2)支持Linux、Mac OsX、FreeBSD、QNX和Windows等操作系统。

(3)支持主、从模式。

(4)稍加修改就可以在裸机、FreeRTOS、RT-Thread上使用。

2、Libmodbus的下载和源码介绍

(1)下载地址:https://github.com/stephane/libmodbus

(2)源码浏览

在目录 “ libmodbus-3.1.10\libmodbus-3.1.10\src” 下存在如下文件:

  • modbus.c:modbus的统一接口层。
  • modbus-data.c:一些数据处理的辅助函数。
  • modbus-rtu.c:ModbusRtu模式相关程序,一般使用串口。
  • modbus-tcp.c:ModbusTcp相关协议,使用以太网。

(3)源码分层

        libmodbus通过抽象出这些层次,提供了一个跨平台的API,使得在不同硬件和平台上实现Modbus通信变得更加容易。用户在使用libmodbus时,可以根据需要选择不同的后端(如RTU或TCP),而核心层和应用层的代码可以保持不变,从而提高了代码的可移植性和可维护性。

APP 应用层(APP):这一层是用户直接交互的层面,它知道要做什么,比如主设备需要读写哪些寄存器,从设备需要提供或接收什么数据。
modbus.c Modbus核心层:这一层向上提供接口函数,向下调用底层代码构造数据包,发送、接收数据包并解析。它负责Modbus协议的具体实现,包括读写操作的逻辑处理。
modbus-rtu.c、modbus-tcp.c 后端(数据传输层):这一层负责硬件相关的数据封包与发送、接收与解包。它处理与不同网络(如串行RTU或以太网TCP IPv4/IPv6)的通信细节。
物理层 串口或以太网等

3、modbus.c文件核心函数介绍

(1)modbus_read_bits函数

  • 函数原型:int modbus_read_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
  • 函数功能:读布尔量(读数字量输出)
参数 说明
modbus_t *ctx 指向Modbus上下文的指针,包含了与Modbus设备通信所需的所有信息。
int addr 要读取的线圈的起始地址。
int nb 要读取的线圈的起始地址
uint8_t *dest 指向目标数组的指针,用于存储读取的线圈状态。
返回值 -1;读取失败;n:读取线圈的梳理nb

(2)modbus_read_input_bits函数

  • 函数原型:int modbus_read_input_bits(modbus_t *ctx, int addr, int nb, uint8_t *dest)
  • 函数功能:读布尔量(读数字量输入/离散输入状态)

(3)modbus_write_bit函数

(4)modbus_write_bits函数

(5)modbus_read_registers函数

  • 函数原型:int modbus_read_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
  • 函数功能:读模拟量(读输出模拟量AO)

(6)modbus_read_input_registers函数

  • 函数原型:int modbus_read_input_registers(modbus_t *ctx, int addr, int nb, uint16_t *dest)
  • 函数功能:读模拟量(读输入模拟量/AI)

(7)modbus_write_register函数

  • 函数原型:int modbus_write_register(modbus_t *ctx, int addr, const uint16_t value)
  • 函数功能:写单个输出模拟量
  • 函数调用了write_single,write_single调用了函数send_msg、_modbus_receive_msg、check_confirmation。
    • send_msg:发送消息
    • _modbus_receive_msg:接收消息
    • check_confirmation:检查

(8)modbus_write_registers函数

  • 函数原型:int modbus_write_registers(modbus_t *ctx, int addr, int nb, const uint16_t *src)
  • 功能:写多个输出模拟量
  • modbus_write_registers调用了函数send_msg、_modbus_receive_msg、check_confirmation。
    • send_msg:发送消息
    • _modbus_receive_msg:接收消息
    • check_confirmation:检查

(9)send_msg函数

  • 函数原型:static int send_msg(modbus_t *ctx, uint8_t *msg, int msg_length)
  • 函数功能:发送消息
  • 调用函数ctx->backend->send_msg_pre(msg, msg_length)
    • 发送消息的准备函数
    • prepare:准备;backend:后端
  • 调用函数ctx->backend->send(ctx, msg, msg_length);
    • 发送真正的消息
    • backend是一个非常重要的结构体,该结构体由modbus-rtu.c或者modbus-tcp构造。

4、modbus-rtu.c核心函数介绍

(1)主要工作就是构造这个结构体。

// clang-format off
const modbus_backend_t _modbus_rtu_backend = {
    _MODBUS_BACKEND_TYPE_RTU,
    _MODBUS_RTU_HEADER_LENGTH,
    _MODBUS_RTU_CHECKSUM_LENGTH,
    MODBUS_RTU_MAX_ADU_LENGTH,
    _modbus_set_slave,
    _modbus_rtu_build_request_basis,
    _modbus_rtu_build_response_basis,
    _modbus_rtu_prepare_response_tid,
    _modbus_rtu_send_msg_pre,
    /* 发送 */
    _modbus_rtu_send,
    _modbus_rtu_receive,
    /* 接收 */
    _modbus_rtu_recv,
    _modbus_rtu_check_integrity,
    _modbus_rtu_pre_check_confirmation,
    _modbus_rtu_connect,
    _modbus_rtu_is_connected,
    _modbus_rtu_close,
    _modbus_rtu_flush,
    _modbus_rtu_select,
    _modbus_rtu_free
};

5、mosbus-tcp核心函数介绍

(1)主要工作就是构造这个结构体。

const modbus_backend_t _modbus_tcp_pi_backend = {
    _MODBUS_BACKEND_TYPE_TCP,
    _MODBUS_TCP_HEADER_LENGTH,
    _MODBUS_TCP_CHECKSUM_LENGTH,
    MODBUS_TCP_MAX_ADU_LENGTH,
    _modbus_set_slave,
    _modbus_tcp_build_request_basis,
    _modbus_tcp_build_response_basis,
    _modbus_tcp_prepare_response_tid,
    _modbus_tcp_send_msg_pre,
    /* 网络发送 */
    _modbus_tcp_send,
    _modbus_tcp_receive,
    /* 网络接收 */
    _modbus_tcp_recv,
    _modbus_tcp_check_integrity,
    _modbus_tcp_pre_check_confirmation,
    _modbus_tcp_pi_connect,
    _modbus_tcp_is_connected,
    _modbus_tcp_close,
    _modbus_tcp_flush,
    _modbus_tcp_select,
    _modbus_tcp_pi_free
};

6、modbus-data.c文件核心函数介绍

(1)bswap_16函数:实现16位无符号整数的字节交换。

  • 将数据从大端序(big-endian)转换为小端序(little-endian),或者从小端序转换为大端序。
#if !defined(bswap_16)
/* 如果bswap_16没有被定义,编译器会发出一个警告信息,提示用户正在使用C函数作为字节交换的后备方案。 */
#  warning "Fallback on C functions for bswap_16"
static inline uint16_t bswap_16(uint16_t x)
{
    return (x >> 8) | (x << 8);
}
#endif

(2)bswap_16函数:实现32位无符号整数的字节交换。

  • 将数据从大端序(big-endian)转换为小端序(little-endian),或者从小端序转换为大端序。
#  warning "Fallback on C functions for bswap_32"
static inline uint32_t bswap_32(uint32_t x)
{
    return (bswap_16(x & 0xffff) << 16) | (bswap_16(x >> 16));
}
#endif

7、modbus_backend结构体

typedef struct _modbus_backend {
    unsigned int backend_type;
    unsigned int header_length;
    unsigned int checksum_length;
    unsigned int max_adu_length;
    int (*set_slave)(modbus_t *ctx, int slave);
    int (*build_request_basis)(
        modbus_t *ctx, int function, int addr, int nb, uint8_t *req);
    int (*build_response_basis)(sft_t *sft, uint8_t *rsp);
    int (*prepare_response_tid)(const uint8_t *req, int *req_length);
    /* 发送消息前的准备工作 */
    int (*send_msg_pre)(uint8_t *req, int req_length);
    /* 发送消息 */
    ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length);
    int (*receive)(modbus_t *ctx, uint8_t *req);
     /* 接收消息 */
    ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length);
    int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length);
    int (*pre_check_confirmation)(modbus_t *ctx,
                                  const uint8_t *req,
                                  const uint8_t *rsp,
                                  int rsp_length);
    /* 连接相关,TCP中需要建立连接 */
    int (*connect)(modbus_t *ctx);            
    unsigned int (*is_connected)(modbus_t *ctx);
    void (*close)(modbus_t *ctx);
    int (*flush)(modbus_t *ctx);
    int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
    void (*free)(modbus_t *ctx);
} modbus_backend_t;

(1)涉及硬件操作的函数

  • ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length);
  • ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length);

8、unit-test-client.c文件

(1)单元测试客户端程序,客户端主动发送消息,然后接收服务器的应答。

(2)根据use_backend确认modbus的通信方式。

if (use_backend == TCP) 
{
    ctx = modbus_new_tcp(ip_or_device, 1502);
} else if (use_backend == TCP_PI) {
    ctx = modbus_new_tcp_pi(ip_or_device, "1502");
} else {
    ctx = modbus_new_rtu(ip_or_device, 115200, 'N', 8, 1);
}
if (ctx == NULL) {
    fprintf(stderr, "Unable to allocate libmodbus context\n");
    return -1;
}

(3)modbus_new_rtu(ip_or_device, 115200, 'N', 8, 1);

  • ip_or_device:串口设备号
  • 115200:串口波特率
  • 'N': 校验相关
  • 8:数据位
  • 1:停止位
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐