libmodbus 使用示例

介绍

​​libmodbus​​ 是一个免费、开源的软件库,用于使用 ​​Modbus​​ 协议进行通信。它用 C 语言编写,旨在提供一个简单、稳定且跨平台的解决方案,让应用程序能够轻松地作为 Modbus 主站(客户端)或从站(服务器)运行。

源码链接

Modbus使用示例 (ModbusMaster)

c++ 复制代码
#pragma once

#include <modbus/modbus.h>
#include <unistd.h>

#include <cstring>
#include <memory>
#include <string>

#define LOG_ERROR(fmt, ...)                              \
  do {                                                   \
    fprintf(stderr, "[ERROR] " fmt "\n", ##__VA_ARGS__); \
  } while (0)

#define LOG_INFO(fmt, ...)                              \
  do {                                                  \
    fprintf(stdout, "[INFO] " fmt "\n", ##__VA_ARGS__); \
  } while (0)

class ModbusMaster {
 public:
  struct Option {
    std::string path = "/dev/ttyUSB0";
    int baud = 9600;
    char parity = 'N';
    int databit = 8;
    int stopbit = 1;
    int debug = 1;
    int modbus_cmd_delay_ms = 5;
  };

 public:
  ModbusMaster(const Option& option) : option_(option) {}
  ~ModbusMaster();

  bool Open() {
    if (ctx == nullptr) {
      return Connect();
    }
    LOG_INFO("modbus already opened");
    return true;
  }

  bool Close() {
    if (ctx != nullptr) {
      modbus_close(ctx);
      modbus_free(ctx);
      ctx = nullptr;
      LOG_INFO("modbus closed");
    }
    return true;
  }

  virtual void RunOnce() {
    if (this->ctx == nullptr) {
      LOG_ERROR("modbus ctx is null, please check connect.");
      return;
    }

    uint8_t data[UINT8_MAX];
    memset(data, 0, sizeof(data));
    int ret = 0;

    // Example: Read/Write Slave 1
    modbus_set_slave(ctx, 0);

    // read
    ret = modbus_read_input_bits(ctx, 0, 4, data);
    if (ret < 0) {
      LOG_ERROR("modbus_read_input_bits failed : %s", modbus_strerror(errno));
    } else {
      LOG_INFO("modbus_read_input_bits success:");
      for (int i = 0; i < ret; i++) {
        LOG_INFO("  bit[%d] = %d", i, data[i]);
      }
    }

    // Add delay between commands
    usleep(option_.modbus_cmd_delay_ms * 1000);

    // write
    uint8_t buff[4] = {0, 1, 0, 1};
    ret = modbus_write_bits(ctx, 0, 4, buff);
    if (ret < 0) {
      LOG_ERROR("modbus_write_bits failed : %s", modbus_strerror(errno));
    }
  }

  //
 private:
  bool Connect() {
    this->ctx =
        modbus_new_rtu(option_.path.c_str(), option_.baud, option_.parity,
                       option_.databit, option_.stopbit);

    if (this->ctx == nullptr) {
      LOG_ERROR("unable creat modbus : %s", option_.path.c_str());
      return false;
    }

    {
      int a = modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS232);
      int c = modbus_rtu_get_serial_mode(ctx);
      if (c == -1) {
        LOG_ERROR("set rs485 failed : %s ", modbus_strerror(errno));
        return false;
      }

      int b = modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_UP);
      if (b == -1) {
        LOG_ERROR("set rts failed : %s ", modbus_strerror(errno));
        return false;
      }
    }

    {
      int a = modbus_set_slave(ctx, 1);
      if (a == -1) {
        LOG_ERROR("set slave failed : %s ", modbus_strerror(errno));
        return false;
      }

      modbus_set_debug(ctx, option_.debug);

      modbus_set_response_timeout(ctx, 0, 100 * 1000);

      a = modbus_connect(ctx);
      if (a == -1) {
        LOG_ERROR("modbus_connect failed : %s ", modbus_strerror(errno));
        return false;
      }
    }

    LOG_INFO("modbus start");
    return true;
  }

 private:
  Option option_;
  modbus_t* ctx = nullptr;
};

注意事项:

  • 在 Connect() 过程中:

    • mode 需要设置成 MODBUS_RTU_RS232 (实际接口为232或485),设置成 RS485 会报错无法通信;
      • 参考以下源码实现 可以看到设置成 RS232 或 RS485 的区别;
      • 推测 工控机厂家 把 485,232 的驱动都配置成 通用的 232 串口设备;
      • 这一块需要参考 工控机厂家 提供的 资料进行判断;
    c++ 复制代码
    /// src/modbus-rtu.c : modbus_rtu_set_serial_mode
            if (mode == MODBUS_RTU_RS485) {
              // Get
              if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
                  return -1;
              }
              // Set
              rs485conf.flags |= SER_RS485_ENABLED;
              if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
                  return -1;
              }
    
              ctx_rtu->serial_mode = MODBUS_RTU_RS485;
              return 0;
          } else if (mode == MODBUS_RTU_RS232) {
              /* Turn off RS485 mode only if required */
              if (ctx_rtu->serial_mode == MODBUS_RTU_RS485) {
                  /* The ioctl call is avoided because it can fail on some RS232 ports */
                  if (ioctl(ctx->s, TIOCGRS485, &rs485conf) < 0) {
                      return -1;
                  }
                  rs485conf.flags &= ~SER_RS485_ENABLED;
                  if (ioctl(ctx->s, TIOCSRS485, &rs485conf) < 0) {
                      return -1;
                  }
              }
              ctx_rtu->serial_mode = MODBUS_RTU_RS232;
              return 0;
          }
    • MODBUS_RTU_RTS_UP 一般需要设置,根据 工控机厂家 提供的资料示例来配;
    • debug 测试的时候打开,可以看到详细的日志和数据;
    • timeout 根据实际情况设置,这里默认设置成 100ms;
  • 在运行过程中 RunOnce :

    • 指令发送间需要有间隔时间,不然有的从站会无响应,数值大小根据从站厂家提供 或 自己测试;
    • 运行函数要与实际对象字典命令字对应,可以看 debug 输出的数据看是否正确;
    • 根据实际情况配置要读写的字典对象,注意一点波特率低的时候读写是比较慢的,不要高频进行操作;

Modbus 库连接

  • libmodbus 是通过 pkgconfig 来查找连接的,使用 -lmodbus 进行连接,位置在 ./lib/x86_64-linux-gnu/pkgconfig/libmodbus.pc
  • 使用 cmake 的话一般 给modbus库配置 modbusConfig.cmake , modbusConfigVersion.cmake 方便查找;

Modbus 基本数据区介绍

Modbus 设备中定义的四种基本数据区,这是功能码存在意义的基础:

数据区名称 对象类型 访问权限 含义 举例
线圈 位(1 bit) 读/写 可开关的二进制状态 继电器的开(1)/关(0),灯的亮/灭
离散输入 位(1 bit) 只读 只读的二进制状态 开关状态、故障报警信号
保持寄存器 字(16 bit) 读/写 可读写的数值数据 温度设定值、速度设定值、配置参数
输入寄存器 字(16 bit) 只读 只读的数值数据 温度测量值、压力传感器读数、流量值

功能码就是用来操作这些数据区的指令。


功能码与 libmodbus 函数对照表

下表清晰地展示了标准功能码、其含义以及在 libmodbus 中对应的主站(客户端)常用函数

功能码(十进制) 功能码(十六进制) 功能名称 操作类型 操作数据区 libmodbus 主站函数
1 0x01 读线圈 线圈 modbus_read_bits
2 0x02 读离散输入 离散输入 modbus_read_input_bits
3 0x03 读保持寄存器 保持寄存器 modbus_read_registers
4 0x04 读输入寄存器 输入寄存器 modbus_read_input_registers
5 0x05 写单个线圈 线圈 modbus_write_bit
6 0x06 写单个寄存器 保持寄存器 modbus_write_register
15 0x0F 写多个线圈 线圈 modbus_write_bits
16 0x10 写多个寄存器 保持寄存器 modbus_write_registers
相关推荐
·白小白2 小时前
力扣(LeetCode) ——118.杨辉三角(C++)
c++·算法·leetcode
拾光Ծ2 小时前
【Linux】“ 权限 “ 与相关指令
linux·运维·服务器
硬核子牙2 小时前
调试器是怎么让代码停下来的
linux
To_再飞行2 小时前
Linux Bash(一)
linux·运维·服务器·bash
LCG元3 小时前
保姆级教程:CentOS 7/8 部署Nginx + MySQL + PHP(LEMP)环境,从零开始到上线项目
linux
sulikey3 小时前
C++的STL:深入理解 C++ 的 std::initializer_list
开发语言·c++·stl·list·initializerlist·c++标准库
疯癫的老码农3 小时前
【Linux环境下安装】SpringBoot应用环境安装(五)-milvus安装
linux·spring boot·milvus
代大大3 小时前
sciter.js 之cpp使用教程(1)
c++·前端框架
仰泳的熊猫3 小时前
LeetCode:207. 课程表
数据结构·c++·算法·leetcode