libmodbus是C언어生态中最流行、最成熟的Modbus프로토콜库,由Stéphane Raimbault개발维护,广泛적용于산업 자동화、嵌入式系统、物联网게이트웨이等领域。本文详细介绍libmodbus库的버전检测方法、核心功能、API使用、编译安装以及完整的예제代码,帮助개발者快速掌握libmodbus的使用方法。
一、libmodbus库개요
1.1 核心特性
- 多프로토콜支持:Modbus RTU、Modbus TCP、Modbus ASCII三种프로토콜
- 主从兼备:既可作为클라이언트(마스터),也可作为服务端(슬레이브)
- 跨平台:支持Linux、Windows、Mac OS、FreeBSD、嵌入式Linux等
- 功能完整:支持01-16所有표준기능 코드,以及部分自定义기능 코드
- 高性能:C언어实现,자료占用小,适合嵌入式환경
- 线程安全:每个modbus_t上下文独立,多线程使用需各自创建实例
- 오픈소스무료:LGPL 2.1프로토콜,可무료用于商业项目
- 문서完善:官方문서详细,예제丰富
1.2 버전历史
| 버전 | 发布时间 | 主要更新 |
|---|---|---|
| 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 = dl열기("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 (홈brew)
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 读操作
/* 기능 코드01:读코일 */
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] ? "吸合" : "断开");
}
}
/* 기능 코드02:读이산 입력 */
uint8_t tab_inputs[MODBUS_MAX_READ_BITS];
rc = modbus_read_input_bits(ctx, 0, 4, tab_inputs);
/* 기능 코드03:读유지 레지스터 */
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("읽기到%d个레지스터:\n", rc);
for (int i = 0; i < rc; i++) {
printf(" 레지스터%d:%d\n", i, tab_reg[i]);
}
}
/* 기능 코드04:读입력 레지스터 */
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("온도:%.1f℃,湿度:%.1f%%\n", temperature, humidity);
}
4.3 写操作
/* 기능 코드05:写单个코일 */
rc = modbus_write_bit(ctx, 0, TRUE); // 吸合
if (rc == -1) {
fprintf(stderr, "写코일失败:%s\n", modbus_strerror(errno));
}
modbus_write_bit(ctx, 0, FALSE); // 断开
/* 기능 코드06:写单个레지스터 */
rc = modbus_write_register(ctx, 0, 1234);
if (rc == -1) {
fprintf(stderr, "写레지스터失败:%s\n", modbus_strerror(errno));
}
/* 기능 코드15:写多个코일 */
uint8_t tab_write_coils[] = {TRUE, FALSE, TRUE, FALSE};
rc = modbus_write_bits(ctx, 0, 4, tab_write_coils);
/* 기능 코드16:写多个레지스터 */
uint16_t tab_write_reg[] = {100, 200, 300};
rc = modbus_write_registers(ctx, 0, 3, tab_write_reg);
4.4 浮点数处理
/* 읽기32位浮点数(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(字交换,低字高바이트)
// 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 (字交换,低字在前)
// 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", // 시리얼 포트设备
9600, // 통신 속도
'N', // 패리티(N/E/O)
8, // 데이터 비트
1); // 정지 비트
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);
// 연결
if (modbus_connect(ctx) == -1) {
fprintf(stderr, "RTU연결失败:%s\n", modbus_strerror(errno));
modbus_free(ctx);
return -1;
}
// 유지 레지스터 읽기 (FC03)
int rc = modbus_read_registers(ctx, 0, 10, regs);
if (rc != -1) {
for (int i = 0; i < rc; i++) {
printf("레지스터%d:%d\n", i, regs[i]);
}
}
modbus_close(ctx);
modbus_free(ctx);
return 0;
}
六、Modbus TCP服务器(슬레이브)实现
#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; // 온도
mb_mapping->tab_registers[1] = 50; // 湿度
mb_mapping->tab_bits[0] = TRUE; // 릴레이1
// 启动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);
/* 读유지 레지스터(带重试) */
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);
/* 写单个레지스터 */
int modbus_util_write_reg(ModbusUtil *mu, int addr, uint16_t value);
/* 写单个코일 */
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, "읽기失败(第%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("온도:%.2f℃\n", temp);
// 읽기10个레지스터
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 常见문제
- 연결超时:检查IP주소、포트、防火墙,确认设备在线
- 返回异常码:检查슬레이브 주소、기능 코드、주소范围是否正确
- RTU시리얼 포트打不开:检查시리얼 포트设备名,确认权限(加入dialout组)
- CRC错误: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秒,RTU시리얼 포트3-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代码통합到实际项目中。
답글 작성