🌐 This page is not yet available in English. Showing the Chinese version. Back to Chinese page.
本文目录
- 1. 一、libmodbus库概述
- 2. 二、版本检测方法
- 3. 三、编译安装
- 4. 四、核心API详解
- 5. 五、Modbus RTU串口通讯
- 6. 六、Modbus TCPServer(slave)实现
- 7. 七、完整工具类封装
- 8. 八、编译与链接
- 9. 九、常见问题与最佳实践
- 10. 十、libmodbus与其他C语言Modbus库对比
libmodbus是C语言生态中最流行、最成熟的Modbus协议库,由Stéphane Raimbault开发维护,广泛应用于工业自动化、嵌入式系统、物联网网关等领域。本文详细介绍libmodbus库的版本检测方法、核心功能、API使用、编译安装以及完整的示例代码,帮助开发者快速掌握libmodbus的使用方法。
一、libmodbus库概述
1.1 核心特性
- 多协议支持:Modbus RTU、Modbus TCP、Modbus ASCII三种协议
- 主从兼备:既可作为客户端(master),也可作为服务端(slave)
- 跨平台:支持Linux、Windows、Mac OS、FreeBSD、embeddedLinux等
- 功能完整:支持01-16所有标准功能码,以及部分自定义功能码
- 高性能:C语言实现,资源占用小,适合嵌入式环境
- 线程安全:每个modbus_t上下文独立,多线程使用需各自创建实例
- 开源免费:LGPL 2.1协议,可免费用于商业项目
- 文档完善:官方文档详细,示例丰富
1.2 版本历史
| 版本 | Published | 主要更新 |
|---|---|---|
| 3.1.10 | 2023年 | 最新稳定版,修复多个bug |
| 3.1.0 | 2020年 | 增加Modbus TCP服务器支持,改进RTU |
| 3.0.0 | 2018年 | 重大重构,API变化 |
| 2.9.3 | 2017年 | 2.x系列最后版本 |
| 2.0.0 | 2012年 | 增加TCP支持,改进RTU |
二、版本检测方法
2.1 编译时版本检测
#include <stdio.h>
#include <modbus.h>
int main() {
// libmodbus提供了版本宏
printf("libmodbus版本:%s\n", LIBMODBUS_VERSION_STRING);
printf("主版本号:%d\n", LIBMODBUS_VERSION_MAJOR);
printf("次版本号:%d\n", LIBMODBUS_VERSION_MINOR);
printf("补丁版本号:%d\n", LIBMODBUS_VERSION_MICRO);
// 版本号数值形式,便于比较(部分版本可能未定义此宏)
#ifdef LIBMODBUS_VERSION
printf("版本数值:%d\n", LIBMODBUS_VERSION);
#endif
// 示例输出:
// libmodbus版本:3.1.10
// 主版本号:3
// 次版本号:1
// 补丁版本号:10
// 版本数值:3.1.10
return 0;
}
2.2 运行时版本检测
#include <stdio.h>
#include <modbus.h>
#include <dlfcn.h>
/* 运行时检测libmodbus版本(用于动态链接库) */
void check_libmodbus_version() {
// 方法1:使用版本宏(编译时确定)
printf("编译时版本:%s\n", LIBMODBUS_VERSION_STRING);
// 方法2:通过pkg-config查询(命令行)
// pkg-config --modversion libmodbus
// 方法3:检查特定函数是否存在
// modbus_new_tcp_pi是3.0+版本的函数
void *handle = dlopen("libmodbus.so", RTLD_LAZY);
if (handle) {
void *func = dlsym(handle, "modbus_new_tcp_pi");
if (func) {
printf("检测到modbus_new_tcp_pi函数,版本>=3.0\n");
} else {
printf("未检测到modbus_new_tcp_pi函数,版本<3.0\n");
}
dlclose(handle);
}
}
int main() {
check_libmodbus_version();
return 0;
}
2.3 命令行版本检测
# 方法1:pkg-config
pkg-config --modversion libmodbus
# 输出:3.1.10
# 方法2:查看库文件
ls -la /usr/lib/x86_64-linux-gnu/libmodbus*
# 输出:libmodbus.so.5 -> libmodbus.so.5.1.0
# 方法3:dpkg(Debian/Ubuntu)
dpkg -l | grep libmodbus
# 输出:ii libmodbus-dev 3.1.10-1 amd64
# 方法4:rpm(CentOS/RHEL)
rpm -qa | grep modbus
# 输出:libmodbus-3.1.10-1.el8.x86_64
# 方法5:编译测试程序
cat > test_version.c << 'EOF'
#include <stdio.h>
#include <modbus.h>
int main() { printf("%s\n", LIBMODBUS_VERSION_STRING); return 0; }
EOF
gcc test_version.c -o test_version `pkg-config --cflags --libs libmodbus`
./test_version
# 输出:3.1.10
三、编译安装
3.1 Linux源码编译
# 安装依赖
sudo apt-get install build-essential autoconf automake libtool
# 下载源码
wget https://github.com/stephane/libmodbus/archive/refs/tags/v3.1.10.tar.gz
tar xzf v3.1.10.tar.gz
cd libmodbus-3.1.10
# 编译安装
./autogen.sh
./configure --prefix=/usr/local
make -j4
sudo make install
sudo ldconfig
# 验证安装
pkg-config --modversion libmodbus
# 输出:3.1.10
3.2 包管理器安装
# Debian/Ubuntu
sudo apt-get install libmodbus-dev
# CentOS/RHEL
sudo yum install libmodbus-devel
# Fedora
sudo dnf install libmodbus-devel
# Arch Linux
sudo pacman -S libmodbus
# Mac OS (Homebrew)
brew install libmodbus
3.3 Windows编译
# 使用MSYS2/MinGW编译
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-libmodbus
# 或者使用CMake
mkdir build
cd build
cmake .. -G "MinGW Makefiles"
mingw32-make
mingw32-make install
四、核心API详解
4.1 上下文创建与连接
#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
#include <errno.h>
int main() {
modbus_t *ctx;
int rc;
/* === 创建TCP上下文 === */
ctx = modbus_new_tcp("192.168.1.100", 502);
if (ctx == NULL) {
fprintf(stderr, "创建TCP上下文失败:%s\n", modbus_strerror(errno));
return -1;
}
/* === 创建RTU上下文 === */
// ctx = modbus_new_rtu("/dev/ttyUSB0", 9600, 'N', 8, 1);
// modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
/* === 设置从站地址 === */
modbus_set_slave(ctx, 1);
/* === 设置调试模式(打印通讯数据) === */
modbus_set_debug(ctx, TRUE);
/* === 设置响应超时 === */
struct timeval response_timeout;
response_timeout.tv_sec = 3;
response_timeout.tv_usec = 0;
modbus_set_response_timeout(ctx, &response_timeout);
/* === 建立连接 === */
rc = modbus_connect(ctx);
if (rc == -1) {
fprintf(stderr, "连接失败:%s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
printf("连接成功!\n");
/* === 业务逻辑 === */
// ...
/* === 关闭连接 === */
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
4.2 读操作
/* function code01:读线圈 */
uint8_t tab_coils[MODBUS_MAX_READ_BITS]; // 最多2000位
rc = modbus_read_bits(ctx, 0, 8, tab_coils);
if (rc == -1) {
fprintf(stderr, "读线圈失败:%s\n", modbus_strerror(errno));
} else {
printf("读取到%d个线圈状态:\n", rc);
for (int i = 0; i < rc; i++) {
printf(" 线圈%d:%s\n", i, tab_coils[i] ? "吸合" : "断开");
}
}
/* function code02:read discrete inputs */
uint8_t tab_inputs[MODBUS_MAX_READ_BITS];
rc = modbus_read_input_bits(ctx, 0, 4, tab_inputs);
/* function code03:read holding registers */
uint16_t tab_reg[MODBUS_MAX_READ_REGISTERS]; // 最多125个
rc = modbus_read_registers(ctx, 0, 10, tab_reg);
if (rc == -1) {
fprintf(stderr, "读寄存器失败:%s\n", modbus_strerror(errno));
} else {
printf("读取到%dRegisters:\n", rc);
for (int i = 0; i < rc; i++) {
printf(" register%d:%d\n", i, tab_reg[i]);
}
}
/* function code04:read input registers */
uint16_t tab_input_reg[MODBUS_MAX_READ_REGISTERS];
rc = modbus_read_input_registers(ctx, 0, 2, tab_input_reg);
if (rc != -1) {
float temperature = tab_input_reg[0] / 10.0;
float humidity = tab_input_reg[1] / 10.0;
printf("temperature:%.1f℃,湿度:%.1f%%\n", temperature, humidity);
}
4.3 写操作
/* function code05:write single coil */
rc = modbus_write_bit(ctx, 0, TRUE); // 吸合
if (rc == -1) {
fprintf(stderr, "写线圈失败:%s\n", modbus_strerror(errno));
}
modbus_write_bit(ctx, 0, FALSE); // 断开
/* function code06:write single register */
rc = modbus_write_register(ctx, 0, 1234);
if (rc == -1) {
fprintf(stderr, "写寄存器失败:%s\n", modbus_strerror(errno));
}
/* function code15:write multiple coils */
uint8_t tab_write_coils[] = {TRUE, FALSE, TRUE, FALSE};
rc = modbus_write_bits(ctx, 0, 4, tab_write_coils);
/* function code16:write multiple registers */
uint16_t tab_write_reg[] = {100, 200, 300};
rc = modbus_write_registers(ctx, 0, 3, tab_write_reg);
4.4 浮点数处理
/* read32位浮点数(ABCD大端) */
uint16_t regs[2];
modbus_read_registers(ctx, 10, 2, regs);
// 方法1:使用modbus_get_float
float value = modbus_get_float(regs);
printf("浮点数值:%.2f\n", value);
// 方法2:手动转换
uint32_t bits = ((uint32_t)regs[0] << 16) | regs[1];
float value2;
memcpy(&value2, &bits, sizeof(float));
printf("浮点数值:%.2f\n", value2);
/* 写入32位浮点数 */
float write_value = 25.5;
uint16_t write_regs[2];
modbus_set_float(write_value, write_regs);
modbus_write_registers(ctx, 10, 2, write_regs);
/* 不同字节序的浮点数处理(libmodbus 3.1+) */
// modbus_get_float_abcd → ABCD(大端,高字高字节)
// modbus_get_float_cdab → CDAB(Word exchange,低字高字节)
// modbus_get_float_dcba → DCBA(小端,低字低字节)
// modbus_get_float_badc → BADC(字节交换,高字低字节)
// modbus_get_float → 默认等同于 abcd(大端)
//
// 字节序说明(以32位值0x41420000为例,A=0x41,B=0x42,C=0x00,D=0x00):
// ABCD: reg[0]=0x4142, reg[1]=0x0000 (标准大端,高字高字节)
// CDAB: reg[0]=0x0000, reg[1]=0x4142 (Word exchange,低字在前)
// DCBA: reg[0]=0x0000, reg[1]=0x4241 (小端,低字低字节)
// BADC: reg[0]=0x4241, reg[1]=0x0000 (字节交换,高字低字节)
五、Modbus RTU串口通讯
#include <modbus.h>
#include <modbus-rtu.h>
int main() {
modbus_t *ctx;
uint16_t regs[10];
// 创建RTU上下文
ctx = modbus_new_rtu("/dev/ttyUSB0", // Serial Port Device
9600, // Baud rate
'N', // check digit(N/E/O)
8, // data bit
1); // stop bit
if (ctx == NULL) {
fprintf(stderr, "创建RTU上下文失败\n");
return -1;
}
// 设置RS485模式
modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
// MODBUS_RTU_RS232 = RS232模式
// MODBUS_RTU_RS485 = RS485模式
// 设置RTS模式(部分RS485转换器需要)
modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_UP);
// MODBUS_RTU_RTS_NONE = 不控制RTS
// MODBUS_RTU_RTS_UP = 发送时RTS高电平
// MODBUS_RTU_RTS_DOWN = 发送时RTS低电平
// 设置从站地址
modbus_set_slave(ctx, 1);
// connect
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "RTU连接失败:%s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
// Read and hold register
int rc = modbus_read_registers(ctx, 0, 10, regs);
if (rc != -1) {
for (int i = 0; i < rc; i++) {
printf("register%d:%d\n", i, regs[i]);
}
}
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
六、Modbus TCPServer(slave)实现
#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
#include <modbus-tcp.h>
#include <unistd.h>
#define NB_COILS 1024
#define NB_INPUTS 1024
#define NB_REGISTERS 1024
int main() {
modbus_t *ctx;
modbus_mapping_t *mb_mapping;
int server_socket;
uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
int rc;
// 创建TCP上下文(监听所有地址)
ctx = modbus_new_tcp("0.0.0.0", 502);
if (ctx == NULL) {
fprintf(stderr, "创建上下文失败\n");
return -1;
}
// 创建数据映射
mb_mapping = modbus_mapping_new(
NB_COILS, // 线圈数量
NB_INPUTS, // 离散输入数量
NB_REGISTERS, // 保持寄存器数量
NB_REGISTERS // 输入寄存器数量
);
if (mb_mapping == NULL) {
fprintf(stderr, "创建数据映射失败:%s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
// 设置初始值
mb_mapping->tab_registers[0] = 100; // temperature
mb_mapping->tab_registers[1] = 50; // 湿度
mb_mapping->tab_bits[0] = TRUE; // Relay1
// 启动TCP监听
server_socket = modbus_tcp_listen(ctx, 5); // 最大5个连接
if (server_socket == -1) {
fprintf(stderr, "监听失败:%s\n", modbus_strerror(errno));
modbus_mapping_free(mb_mapping);
modbus_free(ctx);
return -1;
}
printf("Modbus TCP服务器已启动,监听端口:502\n");
// 主循环
for (;;) {
// 接受客户端连接
int client_socket = modbus_tcp_accept(ctx, &server_socket);
if (client_socket == -1) {
continue;
}
printf("客户端连接\n");
// 处理客户端请求
for (;;) {
rc = modbus_receive(ctx, query);
if (rc > 0) {
// 回复请求
modbus_reply(ctx, query, rc, mb_mapping);
} else {
// 连接断开或错误
break;
}
}
printf("客户端断开\n");
close(client_socket);
}
close(server_socket);
modbus_mapping_free(mb_mapping);
modbus_free(ctx);
return 0;
}
七、完整工具类封装
/* modbus_util.h */
#ifndef MODBUS_UTIL_H
#define MODBUS_UTIL_H
#include <modbus.h>
typedef struct {
modbus_t *ctx;
const char *host;
int port;
int slave_id;
} ModbusUtil;
/* 创建并连接 */
ModbusUtil* modbus_util_connect(const char *host, int port, int slave_id);
/* 断开连接 */
void modbus_util_disconnect(ModbusUtil *mu);
/* read holding registers(带重试) */
int modbus_util_read_regs(ModbusUtil *mu, int addr, int count, uint16_t *regs);
/* 读32位浮点数 */
float modbus_util_read_float(ModbusUtil *mu, int addr);
/* 写32位浮点数 */
int modbus_util_write_float(ModbusUtil *mu, int addr, float value);
/* write single register */
int modbus_util_write_reg(ModbusUtil *mu, int addr, uint16_t value);
/* write single coil */
int modbus_util_write_coil(ModbusUtil *mu, int addr, int value);
/* 读线圈 */
int modbus_util_read_coils(ModbusUtil *mu, int addr, int count, uint8_t *coils);
#endif
/* modbus_util.c */
#include "modbus_util.h"
#include <stdio.h>
#include <unistd.h>
ModbusUtil* modbus_util_connect(const char *host, int port, int slave_id) {
ModbusUtil *mu = malloc(sizeof(ModbusUtil));
mu->host = host;
mu->port = port;
mu->slave_id = slave_id;
for (int attempt = 0; attempt < 3; attempt++) {
mu->ctx = modbus_new_tcp(host, port);
if (mu->ctx == NULL) continue;
modbus_set_slave(mu->ctx, slave_id);
struct timeval tv = {3, 0};
modbus_set_response_timeout(mu->ctx, &tv);
if (modbus_connect(mu->ctx) != -1) {
printf("连接成功:%s:%d\n", host, port);
return mu;
}
modbus_free(mu->ctx);
sleep(1);
}
free(mu);
return NULL;
}
void modbus_util_disconnect(ModbusUtil *mu) {
if (mu) {
modbus_close(mu->ctx);
modbus_free(mu->ctx);
free(mu);
}
}
int modbus_util_read_regs(ModbusUtil *mu, int addr, int count, uint16_t *regs) {
for (int attempt = 0; attempt < 3; attempt++) {
int rc = modbus_read_registers(mu->ctx, addr, count, regs);
if (rc != -1) return rc;
fprintf(stderr, "Read failed(No.%d次):%s\n", attempt+1, modbus_strerror(errno));
modbus_close(mu->ctx);
modbus_connect(mu->ctx);
}
return -1;
}
float modbus_util_read_float(ModbusUtil *mu, int addr) {
uint16_t regs[2];
if (modbus_util_read_regs(mu, addr, 2, regs) != 2) return 0;
return modbus_get_float(regs);
}
int modbus_util_write_float(ModbusUtil *mu, int addr, float value) {
uint16_t regs[2];
modbus_set_float(value, regs);
return modbus_write_registers(mu->ctx, addr, 2, regs);
}
int modbus_util_write_reg(ModbusUtil *mu, int addr, uint16_t value) {
return modbus_write_register(mu->ctx, addr, value);
}
int modbus_util_write_coil(ModbusUtil *mu, int addr, int value) {
return modbus_write_bit(mu->ctx, addr, value);
}
int modbus_util_read_coils(ModbusUtil *mu, int addr, int count, uint8_t *coils) {
return modbus_read_bits(mu->ctx, addr, count, coils);
}
/* 使用示例 */
int main() {
ModbusUtil *mb = modbus_util_connect("192.168.1.100", 502, 1);
if (!mb) {
fprintf(stderr, "连接失败\n");
return -1;
}
// 读取温度
float temp = modbus_util_read_float(mb, 10);
printf("temperature:%.2f℃\n", temp);
// read10个寄存器
uint16_t regs[10];
modbus_util_read_regs(mb, 0, 10, regs);
// 控制继电器
modbus_util_write_coil(mb, 0, TRUE);
modbus_util_disconnect(mb);
return 0;
}
八、编译与链接
# 简单编译
gcc main.c -o main `pkg-config --cflags --libs libmodbus`
# CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(modbus_demo)
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBMODBUS REQUIRED libmodbus)
add_executable(main main.c)
target_include_directories(main PRIVATE ${LIBMODBUS_INCLUDE_DIRS})
target_link_libraries(main ${LIBMODBUS_LIBRARIES})
# Makefile
CC = gcc
CFLAGS = `pkg-config --cflags libmodbus` -Wall
LDFLAGS = `pkg-config --libs libmodbus`
main: main.c
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
clean:
rm -f main
九、常见问题与最佳实践
9.1 common problems
- 连接超时:检查IPaddress、端口、防火墙,确认设备在线
- 返回异常码:检查从站地址、function code、地址范围是否正确
- RTU串口打不开:检查串口设备名,确认权限(加入dialout组)
- CRCError:RTU模式下libmodbus自动处理CRC,不要手动添加
- 浮点数解析错误:确认设备字节序,使用对应的函数(modbus_get_float_abcd/cdab/dcba/badc)
- 多线程问题:每个线程使用独立的modbus_t上下文,不要共享
- 内存泄漏:使用modbus_mapping_free释放映射,modbus_free释放上下文
9.2 最佳实践
- 使用pkg-config:通过pkg-config获取编译参数,避免路径问题
- 批量读取:尽量一次读取多个连续寄存器,减少通讯次数
- 合理超时:局域网1-3秒,RTUserial port3-5秒
- 添加重试:网络不稳定时自动重试2-3次
- 错误处理:每次调用检查返回值,使用modbus_strerror获取错误描述
- 调试模式:开发阶段开启modbus_set_debug,查看原始通讯数据
- 资源释放:程序退出前调用modbus_close和modbus_free
- 版本兼容:使用LIBMODBUS_VERSION_CHECK宏检测版本,确保API兼容
十、libmodbus与其他C语言Modbus库对比
| 库名 | 协议支持 | 服务端支持 | 活跃度 | 适用场景 |
|---|---|---|---|---|
| libmodbus | RTU/TCP/ASCII | 支持 | 高 | 大多数项目首选 |
| freemodbus | RTU/ASCII | 仅从站 | 中 | 嵌入式从站设备 |
| modbus-arduino | RTU/TCP | 支持 | 中 | Arduino平台 |
| mbus | RTU/TCP | 仅主站 | 低 | 简单主站应用 |
libmodbus是C语言Modbus开发的首选库,其完善的功能和活跃的社区使其在工业自动化、嵌入式系统、物联网网关等领域得到广泛应用。本文提供的版本检测方法、API详解和工具类封装可直接用于项目开发,建议先使用Modbus Poll确认设备通讯正常,再将C代码集成到实际项目中。
Leave a Reply