德洛瓦:被弃之族免安装正式版
3.63G · 2025-10-22
libmodbus 是一个免费、开源的软件库,用于使用 Modbus 协议进行通信。它用 C 语言编写,旨在提供一个简单、稳定且跨平台的解决方案,让应用程序能够轻松地作为 Modbus 主站(客户端)或从站(服务器)运行。
sudo apt install libmodbus-dev
#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() 过程中:
MODBUS_RTU_RS232
(实际接口为232或485),设置成 RS485 会报错无法通信;
/// 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 :
pkgconfig
来查找连接的,使用 -lmodbus
进行连接,位置在 ./lib/x86_64-linux-gnu/pkgconfig/libmodbus.pc
modbusConfig.cmake
, modbusConfigVersion.cmake
方便查找;Modbus 设备中定义的四种基本数据区,这是功能码存在意义的基础:
数据区名称 | 对象类型 | 访问权限 | 含义 | 举例 |
---|---|---|---|---|
线圈 | 位(1 bit) | 读/写 | 可开关的二进制状态 | 继电器的开(1)/关(0),灯的亮/灭 |
离散输入 | 位(1 bit) | 只读 | 只读的二进制状态 | 开关状态、故障报警信号 |
保持寄存器 | 字(16 bit) | 读/写 | 可读写的数值数据 | 温度设定值、速度设定值、配置参数 |
输入寄存器 | 字(16 bit) | 只读 | 只读的数值数据 | 温度测量值、压力传感器读数、流量值 |
功能码就是用来操作这些数据区的指令。
下表清晰地展示了标准功能码、其含义以及在 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 |
华为鸿蒙 HarmonyOS 6 支持与苹果 iOS / iPadOS / macOS 互传体验
Netflix 宣布全力投入 AI:“能帮人类把故事讲得更好”