介绍
libmodbus 是一个免费、开源的软件库,用于使用 Modbus 协议进行通信。它用 C 语言编写,旨在提供一个简单、稳定且跨平台的解决方案,让应用程序能够轻松地作为 Modbus 主站(客户端)或从站(服务器)运行。
源码链接
- source : github.com/stephane/li...
- apt 安装(debian,ubuntu系统):
sudo apt install libmodbus-dev
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;
- mode 需要设置成
-
在运行过程中 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 |