libmodbus是C语言生态中最流行、最成熟的Modbusプロトコル库,由Stéphane Raimbault开发维护,广泛应用于工业自動化、エンベデッド·イン系统、IoTゲートウェイ等领域。本記事详细介绍libmodbus库的版本检测方法、核心功能、API使用、编译インストール以及完全的サンプルコード,帮助開発者たちはスピードマスター。libmodbus的使用方法。
一、libmodbus库概述
1.1 核心特性
- 多协议サポート:Modbus RTU、Modbus TCP、Modbus ASCII三种协议
- 主从兼备:既可作为クライアント側は(master),也可作为サーバー側。(slave)
- 跨プラットフォーム:サポートLinux、Windows、Mac OS、FreeBSD、embeddedLinux等
- 功能完全:サポート01-16すべての標準機能コード,以及部分自定义機能コード
- 高いパフォーマンスを:C语言実装,リソース占用小,适合エンベデッド·イン环境
- 线程セキュリティ:每个modbus_tContextより独立,多线程使用需各自作成实例
- 开源無料: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 (Modbus中国語ネットワーク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 Contextより作成与连接
#include <stdio.h>
#include <stdlib.h>
#include <modbus.h>
#include <errno.h>
int main() {
modbus_t *ctx;
int rc;
/* === 作成TCPContextより === */
ctx = modbus_new_tcp("192.168.1.100", 502);
if (ctx == NULL) {
fprintf(stderr, "作成TCPContextより失敗:%s\n", modbus_strerror(errno));
return -1;
}
/* === 作成RTUContextより === */
// 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];
// 作成RTUContextより
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, "作成RTUContextより失敗\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;
// 作成TCPContextより(监听すべてのアドレス)
ctx = modbus_new_tcp("0.0.0.0", 502);
if (ctx == NULL) {
fprintf(stderr, "作成Contextより失敗\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);
// read101つのレジスタ
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_tContextより,不要共享
- 内存泄漏:使用modbus_mapping_free释放マッピング,modbus_free释放Contextより
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开发的首选库,其完善的功能和活跃的社区使其在工业自動化、エンベデッド·イン系统、IoTゲートウェイ等领域得到广泛应用。本記事提供的版本检测方法、API详解和工具类封装可直接使用する。于项目开发,建议先使用Modbus Poll機器の確認通信は正常,再将C代码集成到实际项目中。
Leave a Reply