简介ewhales-libmodbus-3.1.6-master.zip 是面向嵌入式开发者的 libmodbus 3.1.6 源码包定位服务于需在 arm A7 架构 imx6ull 平台完成 Modbus 通信功能移植与交叉编译的工程场景。压缩包共收录 158 个文件整体大小约 776KB内部既包含 12 份 C 语言核心源码含 modbus.c、modbus-rtu.c、13 个头文件、configure 配置脚本、Makefile 构建体系以及编译生成的 libmodbus.so 动态库也带有大量说明类 txt 文档、协议描述与自动化辅助脚本目录组织规整可直接用于项目环境搭建与源码级调试。目前已有 402 人学习浏览。内容围绕 Modbus RTU/TCP 双协议实现、交叉编译工具链参数配置例如 --hostarm-linux-gnueabihf 的指定方式、源码增强和自定义功能码扩展等核心主题展开读者既能获得可直接编译部署的完整库文件也能结合描述中给出的编译命令和优化思路针对 imx6ull 平台做裁剪与适配为后续接入 PLC、变频器等工业设备提供稳定通信基础。1. 交叉编译 libmodbus 到 ARMimx6ull 上跑 Modbus 的第一道坎做嵌入式网关的朋友基本都撞过这堵墙宿主系统里apt install libmodbus-dev装得很顺代码编译也通过等把可执行文件拷到 imx6ull 板子上一运行就报error while loading shared libraries: libmodbus.so.5明明-lmodbus都链接了。问题就出在你手里的库是 x86 的而不是 ARM 的。这时候你需要一份能在 ARM 环境下编译的源码包我这次拆的就是ewhales-libmodbus-3.1.6-master.zip一份 libmodbus 3.1.6 的源码解决的就是交叉编译、产物移植和运行时依赖的问题。适合三类人正在做 imx6ull 或类似 Cortex-A7 平台 Modbus 协议采集的、被交叉编译工具链折腾到怀疑人生的、想确认 libmodbus 在嵌入式平台到底该怎么编出可靠产物的。2. 准备工作工具链、CMake 版本与 libmodbus 的源码结构2.1 先确认交叉工具链arm-linux-gnueabihf 和 Cortex-A7 的硬浮点imx6ull 的 CPU 是 Cortex-A7ARMv7-A 架构默认支持硬件浮点所以交叉编译时工具链前缀一般选arm-linux-gnueabihf不是arm-linux-gnueabi。这里有个常见误解gnueabi也能编但默认用的是软浮点生成的目标文件里没有 VFP 寄存器指令。如果 libmodbus 编译时用了硬浮点参数最后链接你的业务程序时工具链会报selected processor does not support requeste d special purpose register之类的问题或者干脆在板子上跑出Illegal instruction。我一般在拿到源码包之前先确认工具链是否可用arm-linux-gnueabihf-gcc --version arm-linux-gnueabihf-gcc -v 21 | grep Target:Target:后面显示的是arm-linux-gnueabihf就没问题。接下来检查编译器默认架构参数arm-linux-gnueabihf-gcc -marcharmv7-a -mfloat-abihard -mfpuvfpv3-d16 -print-libgcc-file-name这条命令不是真正编译而是确认工具链能否接受 imx6ull 常用的参数组合。很多老工具链只支持-mfpuneon或者-mfloat-abisoftfp对vfpv3-d16会直接报unrecognized command-line option。如果报错了后续 CMake 配置就得换参数别硬磕。2.2 libmodbus 到底依赖什么比你以为的干净翻 libmodbus 的源码你会发现它核心就是src/下的一堆 C 文件没有第三方依赖只依赖 libc 和系统串口接口。这意味着交叉编译最大的坑不在依赖上而在 CMake 配置和工具链路径上。官方 README 里写着支持 LinuxWindowsMac但到了嵌入式 Linux你需要处理的是modbus_rtu.h、modbus_tcp.h这些头文件对应的底层系统调用是否齐全。imx6ull 的 Linux 内核一般都有标准 termios所以 libmodbus 的 RTU 模式能直接跑。另外libmodbus 3.1.6 的 CMake 最低要求已经从老版本的 2.8 提到了 3.3 以上。我遇到过板子配套 SDK 里的 CMake 还是 3.2配置时直接报CMake 3.3 or higher is required。所以准备工作里要把宿主机的 CMake 也确认一遍cmake --version如果版本低于 3.3不要手动改源码包里的CMakeLists.txt建议直接升级宿主机 CMake或者用 pip 装一个较新的独立版本。后面第 4 章会专门说这个坑。2.3 解压源码包后先看这四个文件拿到ewhales-libmodbus-3.1.6-master.zip解压后不要急着cmake ..先看四个东西unzip ewhales-libmodbus-3.1.6-master.zip cd ewhales-libmodbus-3.1.6-master ls -la cat CMakeLists.txt | head -n 80 ls src/ cat src/CMakeLists.txt | grep -E target_link_libraries|add_libraryCMakeLists.txt头部会告诉你这个版本支持哪些编译选项比如ENABLE_STATIC_LIB、ENABLE_SHARED_LIB、BUILD_TESTING。我们嵌入式场景一般只编静态库或者静态动态都编但优先静态。src/CMakeLists.txt里的add_library和target_link_libraries能看出它在 Windows 下会链接ws2_32在 Linux 下不链接额外库。这一步的意义是让你心里有数libmodbus 没有隐藏依赖后面所有问题都出在你自己的环境上。3. 用 CMake toolchain 文件交叉编译一条命令出产物3.1 写 toolchain.cmake四件事必须写对交叉编译 libmodbus 最干净的方式是给 CMake 一份 toolchain 文件而不是在命令行里手动传一堆变量。我一般会在源码根目录外建一个cmake-toolchains/目录专门放板子的工具链配置。对于 imx6ulltoolchain 文件长这样set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR arm) set(TOOLCHAIN_PREFIX arm-linux-gnueabihf-) set(CMAKE_C_COMPILER ${TOOLCHAIN_PREFIX}gcc) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PREFIX}g) set(CMAKE_FIND_ROOT_PATH /opt/sdk/imx6ull/arm-linux-gnueabihf) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_C_FLAGS ${CMAKE_C_FLAGS} -marcharmv7-a -mfloat-abihard -mfpuvfpv3-d16) set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -marcharmv7-a -mfloat-abihard -mfpuvfpv3-d16)CMAKE_SYSTEM_NAME必须写 Linux否则 libmodbus 的src/里某些平台判断逻辑会走到别的分支。CMAKE_SYSTEM_PROCESSOR写 arm 或者 armv7-a 都行影响的是 CMake 内部的一些判断。CMAKE_FIND_ROOT_PATH是 sysroot 的路径它指向你 SDK 里 ARM 文件系统的根目录这样 CMake 找头文件和库时不会跑去宿主机的/usr/include。MODE_PROGRAM设成NEVER是让 CMake 在宿主上找可执行程序比如 find_package 里的工具而INCLUDE和LIBRARY必须ONLY防止串台。CMAKE_C_FLAGS里的三项必须和工具链默认行为保持一致。有的 SDK 工具链编译时默认就是-mfloat-abihard但你显式写出来能避免后面链接时出现隐式错误。3.2 编译、安装与产物检查别只盯着 .so配置和编译的步骤按下面走mkdir build-arm cd build-arm cmake .. -DCMAKE_TOOLCHAIN_FILE../cmake-toolchains/imx6ull.cmake \ -DCMAKE_BUILD_TYPERelease \ -DENABLE_STATIC_LIBON \ -DENABLE_SHARED_LIBON \ -DBUILD_TESTINGOFF make -j4参数说明ENABLE_STATIC_LIB和ENABLE_SHARED_LIB都打开这样同一份源码能同时产出.a和.so。BUILD_TESTINGOFF不编译测试程序测试程序在交叉环境下没法直接跑还可能有fork()和termios相关的测试逻辑干扰编译。CMAKE_BUILD_TYPERelease开优化-O3对串口读写这种 I/O 密集型库没有负面效果。编译完成后检查产物file src/libmodbus.so.5.1.6 arm-linux-gnueabihf-readelf -A src/libmodbus.so.5.1.6 | grep Tag_ABI_VFP_argsfile命令能看到是ARM架构的 ELFreadelf能看到Tag_ABI_VFP_args为VFP registers说明浮点 ABI 确实是硬浮点。这一步是决定你能不能上板的关键检查不做的话后面链接你的应用时翻车概率极高。接着安装到指定目录make install DESTDIR/tmp/libmodbus-install注意我用了DESTDIR不是直接make install。DESTDIR会把安装路径前拼上一个临时目录这样可以避免把 ARM 的.so装进宿主机的/usr/local/lib。装完之后你会在/tmp/libmodbus-install/usr/local/lib看到libmodbus.so和libmodbus.a。3.3 静态库还是动态库这里直接决定你后续踩不踩坑在 imx6ull 这种资源不太宽裕的板子上我的习惯是默认选静态库。原因有三点第一动态库版本管理麻烦。libmodbus 编译出来的.so.5是带版本号的如果你的板子 rootfs 里没有或者版本不一致运行时会报cannot open shared object file。第二libmodbus本身只有几十 KB静态链接到业务程序里多占的空间可以忽略。第三交叉编译环境下动态库的 rpath 和ldconfig配置很容易出问题你要是忘了把.so拷贝到板子的/usr/lib整个程序就起不来。不过你依然需要编译一份.so用于调试。因为.so可以用ldd查看依赖.a不行。有些同事喜欢纯静态编译遇到程序崩溃时没工具排查这是自己没有给自己留后路。4. 避坑imx6ull 交叉编译 libmodbus 的 5 个高频踩坑现场4.1 工具链找不到明明装了却提示 No such file现象执行arm-linux-gnueabihf-gcc --version正常但 CMake 配置时报CMake Error: CMAKE_C_COMPILER not set, or set to an invalid name。原因toolchain 文件里写的编译器和实际路径不一致或者 shell 环境变量PATH没有把工具链目录加进去CMake 找不到这个程序。解决在 toolchain 文件里写绝对路径不要只写前缀。比如把set(CMAKE_C_COMPILER /opt/sdk/imx6ull/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc)写死。同时先执行which arm-linux-gnueabihf-gcc验证下一条中环境的确能找到。4.2 链接时浮点 ABI 冲突文件格式与架构不匹配现象编译 libmodbus 本身没有错误但链接自己的应用时出现cannot find -lmodbus或者relocation R_ARM_THM_MOVW_ABS_NC against a symbol in section .text这类莫名其妙的问题。原因你最可能用的是 Ubuntu 自带的libmodbus-dev那是 x86 版本的.so。CMake 的find_library找到了宿主系统的头文件和库没有强制限定在 sysroot 里。解决回到第 3 章的 toolchain 文件确认CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY和CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY都写了。另外在你的应用工程里明确指定-L/你的安装目录/lib优先链接你交叉编译出来的库。不要再用-lmodbus裸链接。还有一版是我在 imx6ull 上遇到过最隐晦的libmodbus 本身编译通过但业务程序里还用了 curlcurl 版库是软浮点一链接整个程序就乱了。这种情况只能统一工具链参数把-mfloat-abihard和-mfpu用到所有依赖库上没有捷径。4.3 运行时找不到 libmodbus.so.5编译成功只是第一步现象程序拷贝到板子后执行报error while loading shared libraries: libmodbus.so.5: cannot open shared object file。原因你编的是动态库但板子 rootfs 里没有对应的.so.5文件。很多 SDK 出厂 rootfs 只带了基础库libmodbus 是额外的。解决把libmodbus.so.5.1.6拷贝到板子/usr/lib下然后执行ldconfig。但有的板子 rootfs 没有 ldconfig可以直接在/etc/ld.so.conf里加一行路径也可以做一个符号链接# 在宿主机上拷贝到自己的 rootfs 目录 cp /tmp/libmodbus-install/usr/local/lib/libmodbus.so.5.1.6 ~/rootfs/usr/lib/ cd ~/rootfs/usr/lib ln -s libmodbus.so.5.1.6 libmodbus.so.5如果你用sudo dpkg或者rpm不会走这些但大多数 imx6ull 的出厂 yocto/busybox rootfs 都适用。4.4 CMake 版本太老编译脚本里用了新版特性现象CMake 配置时直接报CMake 3.3 or higher is required. You are running version 3.2.2。原因libmodbus 3.1.6 的CMakeLists.txt里有cmake_minimum_required(VERSION 3.3)而很多老 SDK 自带的是 3.2。解决不要改源码里的版本号因为改低后某些新函数还是会触发 warning甚至导致配置结果不对。正确方式是升级宿主机的 CMake。我一般用 pip 装一个独立版本pip install --user cmake # 确认新版本 ~/.local/bin/cmake --version然后配置时直接用~/.local/bin/cmake。4.5 安装路径污染别把 ARM 产物装到本机系统目录现象make install后发现自己宿主机/usr/local/lib/libmodbus.so被覆盖了所有原生的 x86 程序开始报错或者你下一遍编译都找不到原来的库。原因很多人看了网上的教程直接mkdir build cmake .. make没写交叉编译参数。更糟的是make install没指定 prefix直接装到/usr/local。解决先说规避交叉编译时永远加DESTDIR或者cmake -DCMAKE_INSTALL_PREFIX/opt/imx6ull/arm/usr/local。如果已经污染了回滚的办法是从 apt 重新安装libmodbus-devsudo apt install --reinstall libmodbus-dev但这只能救回 x86 库你交叉编译出来的还想继续用的话去/opt/imx6ull/arm/usr/local里找别再放系统目录。5. 上板验证写一个最小 Modbus RTU 主机程序并按步骤跑通5.1 最小验证程序串口打开、写保持寄存器、读回libmodbus 的 API 比较固定验证交叉编译产物时我习惯写一个最小程序打开串口写 4 个保持寄存器再从同一个从机地址读回比对数据。这样可以确认串口驱动、termios、RTU 帧格式都正常。#include stdio.h #include modbus/modbus.h #include errno.h int main(void) { modbus_t *ctx; uint16_t regs[4] {0x1234, 0x5678, 0x90ab, 0xcdef}; uint16_t read_buf[4]; int rc; // 串口设备 /dev/ttyS49600 8N1从机地址 1 ctx modbus_new_rtu(/dev/ttyS4, 9600, N, 8, 1); if (ctx NULL) { fprintf(stderr, modbus_new_rtu failed: %s\n, modbus_strerror(errno)); return -1; } modbus_set_slave(ctx, 1); // 串口延迟等参数在 RTU 模式需要调小 modbus_set_response_timeout(ctx, 0, 500000); // 0.5s if (modbus_connect(ctx) -1) { fprintf(stderr, connect failed: %s\n, modbus_strerror(errno)); modbus_free(ctx); return -1; } rc modbus_write_registers(ctx, 0, 4, regs); if (rc -1) { fprintf(stderr, write failed: %s\n, modbus_strerror(errno)); } else { rc modbus_read_registers(ctx, 0, 4, read_buf); if (rc 4) { for (int i 0; i 4; i) { printf(reg[%d] 0x%04x\n, i, read_buf[i]); } } } modbus_close(ctx); modbus_free(ctx); return 0; }这个程序中modbus_new_rtu的第一个参数是串口设备名imx6ull 上根据你的设备树可能是/dev/ttyS0或/dev/ttyS4实际查看/dev/serial/by-name确认。modbus_set_response_timeout在调试时设成 0.5 秒足够如果板子上的 USB 转串口有延迟这个值可能需要放宽到 1 秒。5.2 交叉编译验证程序并拷贝到目标板arm-linux-gnueabihf-gcc test_modbus.c -o test_modbus \ -I/tmp/libmodbus-install/usr/local/include/modbus \ -L/tmp/libmodbus-install/usr/local/lib \ -lmodbus -static这里我用了-static直接静态链接libmodbus.a。验证时用静态最省事拷到板子上一跑没有动态库依赖。之后用 scp 或者 TFTP 拷到板子给可执行权限chmod x test_modbus ./test_modbus如果你看到reg[0] 0x1234一直到reg[3] 0xcdef说明这一整套交叉编译链是通的。如果modbus_connect失败先检查串口别名是否存在再看是不是权限问题有些板子默认用户对/dev/ttyS4没有写权限。5.3 回头看为什么我后来都默认静态编译那次之后我又编过好几个版本的 libmodbus每次动态库版本升级都让我头疼。板子上的其它程序依赖了旧的.so.4新的程序要求.so.5同一块板子上不能共存两个版本又不能随便ldconfig默认。从那以后我每次在 imx6ull 上做 Modbus 采集相关程序都默认静态链接 libmodbus只在调试阶段输出动态库版本验证完立刻切到静态。你如果不需要反复换协议库版本也可以直接省掉这些麻烦。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?