:從環(huán)境配置到板端驗證)
簡介libmodbus-3.1.6開源庫的完整源碼包專為在ARM Cortex-A7架構(gòu)的imx6ull嵌入式平臺進(jìn)行Modbus通信移植而整理。面向嵌入式工程師與工業(yè)自動化開發(fā)者涵蓋RTU與TCP/IP兩種工作模式可對接PLC、變頻器、溫控器等現(xiàn)場設(shè)備解決跨平臺Modbus協(xié)議集成、移植與定制問題。壓縮包共158個文件約776KB以C源碼、頭文件、configure配置腳本、Makefile.am及m4自動構(gòu)建文件為主輔以txt說明文檔與po多語言文件并保留libmodbus.so.5.1.0動態(tài)庫產(chǎn)物和crosscompile輔助腳本整體完整度較高便于對照實踐已有402人瀏覽學(xué)習(xí)。通過本包可獲得完整源碼與構(gòu)建工具鏈?zhǔn)纠鞔_arm-linux-gnueabihf下從源碼配置、編譯到安裝的完整流程。還可參考源碼中功能碼處理、錯誤檢查與性能優(yōu)化等實現(xiàn)細(xì)節(jié)按項目需求擴(kuò)展自定義功能碼、增強(qiáng)設(shè)備兼容性從而快速構(gòu)建穩(wěn)定可靠的嵌入式Modbus通信解決方案。1. 交叉編譯libmodbus 3.1.6最容易卡住的不是代碼是環(huán)境錯位libmodbus 3.1.6 在 x86 主機(jī)上編譯幾乎不會出問題可一旦要把它交叉編譯到 ARM 平臺尤其是 imx6ull 這類 Cortex-A7 的工業(yè)板上事情就完全變了樣編譯器前綴不對、configure 把目標(biāo)平臺認(rèn)錯、鏈接時文件格式不匹配、運(yùn)行庫缺失、串口權(quán)限不夠——這些坑和業(yè)務(wù)代碼沒有半點(diǎn)關(guān)系純粹是“把 PC 上的編譯習(xí)慣直接搬到了嵌入式環(huán)境”造成的。我第一次在 Ubuntu 主機(jī)上把 libmodbus 編成動態(tài)庫再用 ARM 工具鏈去鏈接折騰了一個通宵才發(fā)現(xiàn) Makefile 里殘留的全是 x86 的對象文件。從那以后凡是涉及 ARM 移植我都會在動手前把工具鏈、源碼包、目標(biāo)板三件事先理清楚再談 configure 和 make。這篇筆記按這條實際走通的流程來寫從一份 libmodbus-3.1.6-master 源碼包說起覆蓋環(huán)境對齊、configure 參數(shù)、make install、踩坑記錄和板端驗證。適合正要把 Modbus RTU/TCP 能力搬到 ARM 板子上的開發(fā)者編譯前先花十分鐘看完能省掉一整晚的排錯時間。2. 環(huán)境準(zhǔn)備交叉工具鏈、源碼包、目標(biāo)板三件事先對齊交叉編譯最忌諱“拿到源碼就開編”。libmodbus 是典型的 autotools 工程configure 腳本會做一連串編譯探測任何一步探測用的編譯器、鏈接器、參數(shù)不對后面 make 出來的東西都不能要。所以在跑 configure 之前我習(xí)慣先把三個東西確認(rèn)好交叉編譯器能否獨(dú)立執(zhí)行、源碼包是否包含可用的 configure 腳本、目標(biāo)板的架構(gòu)、浮點(diǎn)方式和串口節(jié)點(diǎn)是什么。2.1 交叉編譯器選型與 PATH先確認(rèn)是 gnueabihf 還是 gnueabi先說我用的工具鏈arm-linux-gnueabihf 系列GCC 10.3.0。這個前綴的 hf 代表 hard-float目標(biāo) CPU 帶硬件浮點(diǎn)單元imx6ull 的 Cortex-A7 核符合這個特征。如果你手里的工具鏈?zhǔn)?arm-linux-gnueabi軟浮點(diǎn)或者 arm-none-eabi裸機(jī)版先別急著繼續(xù)前者編出來的程序在硬浮點(diǎn) rootfs 上偶爾能跑后者根本是給無操作系統(tǒng)的單片機(jī)用的不能拿來編 Linux 程序。export PATH/opt/arm-linux-gnueabihf/bin:$PATH export CCarm-linux-gnueabihf-gcc export ARarm-linux-gnueabihf-ar export RANLIBarm-linux-gnueabihf-ranlib arm-linux-gnueabihf-gcc --version這里把編譯器和配套的 ar、ranlib 都導(dǎo)出成環(huán)境變量。libmodbus 的 configure 會用到 CC 做編譯探測Makefile 里的 libtool 會調(diào)用 AR 打包 libmodbus.a、調(diào)用 RANLIB 生成靜態(tài)庫索引這兩個不設(shè)configure 可能“看起來成功”但 make 到一半會報找不到 ar 或 ranlib。設(shè)置完 PATH 后一定要執(zhí)行最后那行--version確認(rèn)工具鏈真的出現(xiàn)在 PATH 里。很多翻車現(xiàn)場就是路徑寫錯configure 找不到 arm-linux-gnueabihf-gcc又靜默退回系統(tǒng) gcc最后編出個 x86 的庫。2.2 解壓源碼包并核對 libmodbus 版本configure 在不在源碼包拿過來先解壓、查看目錄結(jié)構(gòu)、確認(rèn)版本號。我用的這份 libmodbus-3.1.6-master.zip解壓后是標(biāo)準(zhǔn)的 autotools 工程目錄最外層有 configure 或 configure.ac 文件。unzip libmodbus-3.1.6-master.zip cd libmodbus-3.1.6-master ls -la grep -n AC_INIT configure.acgrep -n AC_INIT configure.ac用來核對版本號。3.1.6 這一版在 configure.ac 里應(yīng)該有AC_INIT([libmodbus], [3.1.6], ...)一類的聲明確認(rèn)沒拿錯源碼包。這里有個常見的坑如果從 Git 倉庫直接 Download ZIP倉庫里可能只有 configure.ac 和 Makefile.am沒有生成好的 configure 腳本。因為 configure 是自動生成文件不一定進(jìn)版本庫。遇到這種情況需要先手動生成 configureif [ ! -f configure ]; then autoreconf -i -f fiautoreconf 依賴 autoconf、automake、libtool 三個工具Debian 系發(fā)行版直接apt-get install autoconf automake libtool裝上就行。生成過程會打印一堆宏定義正?,F(xiàn)象最后出現(xiàn)“configure.ac:xx: installing ... ”之類字樣就說明生成完畢。注意如果 configure 文件存在務(wù)必不要手賤再跑 autoreconf重新生成出來的腳本可能和你手上的工具鏈版本不匹配反而把原來能用的腳本覆蓋掉。2.3 目標(biāo)板信息核對架構(gòu)、浮點(diǎn)、串口節(jié)點(diǎn)和庫策略目標(biāo)板是 imx6ull 這一檔的 Cortex-A7 核心板32 位 ARM 架構(gòu)硬浮點(diǎn)。編譯前在主機(jī)側(cè)和板子側(cè)各做一次信息確認(rèn)比對著報錯猜原因快得多。# 主機(jī)側(cè)看交叉編譯器的默認(rèn)目標(biāo) arm-linux-gnueabihf-gcc -v 21 | grep Target # 板子側(cè)看 CPU 型號和串口設(shè)備節(jié)點(diǎn) cat /proc/cpuinfo | grep -E model name|Processor ls /dev/ttymxc*grep Target的輸出應(yīng)該是arm-linux-gnueabihf這就代表工具鏈默認(rèn)生成硬浮點(diǎn) ARM 代碼。板子側(cè)的/dev/ttymxc*是 imx6ull 常見 BSP 里 UART 的設(shè)備名不同的板子可能映射到 ttymxc0、ttymxc1、ttymxc3這個節(jié)點(diǎn)名后面寫測試程序要用現(xiàn)在確認(rèn)好省得驗證時對著一個不存在的 /dev/ttyS0 發(fā)愣。庫策略也要在這一步定下來它直接影響 configure 參數(shù)。我在實際項目中按下面這張表選擇場景推薦參數(shù)理由快速驗證、固件整體燒寫--enable-static --disable-shared編譯出單文件可執(zhí)行程序拷貝方便不依賴板子上的動態(tài)庫環(huán)境多個進(jìn)程復(fù)用同一份庫--enable-shared --disable-static省 flash 空間升級時只替換 .so調(diào)試階段跟蹤問題--enable-shared加modbus_set_debug配合 gdb 和動態(tài)庫符號跟進(jìn)棧我建議第一次移植一律用靜態(tài)編譯。原因很簡單動態(tài)編譯除了 libmodbus.so 之外鏈接器還可能依賴 rootfs 里的 ld-linux、libc.so、libpthread.so任何一個版本不匹配都能讓程序跑不起來。靜態(tài)編譯把庫鏈進(jìn) ELF 里排錯范圍小得多。3. configure 與 makelibmodbus 3.1.6 交叉編譯參數(shù)和產(chǎn)物落位環(huán)境對齊之后進(jìn)入正式編譯。libmodbus 3.1.6 的整個編譯流程可以濃縮成三條命令configure 探測環(huán)境、make 編譯、make install 安裝。難點(diǎn)在 configure 參數(shù)怎么給以及 make 完怎么檢查產(chǎn)物是不是真的是 ARM 格式。3.1 configure 參數(shù)逐項拆解host、build、prefix、enable-static先看完整命令./configure \ --hostarm-linux-gnueabihf \ --buildx86_64-pc-linux-gnu \ --prefix/opt/imx6ull/libmodbus \ --enable-static \ --disable-shared \ CFLAGS-O2 -marcharmv7-a -mfloat-abihard這幾個參數(shù)是交叉編譯的核心。--host告訴 configure“最終運(yùn)行程序的目標(biāo)機(jī)器是 arm-linux-gnueabihf”configure 會在這一階段嘗試用 arm 編譯器編譯一個測試程序--build告訴它“當(dāng)前編譯機(jī)器是 x86_64 Linux”不寫時 configure 會自己猜但交叉編譯場景下最好顯式指定避免猜成 ARM 導(dǎo)致“交叉編譯檢測”邏輯混亂。--prefix/opt/imx6ull/libmodbus是安裝路徑。它決定頭文件和庫文件在主機(jī)側(cè)被安裝到哪里配合后面的 DESTDIR 可以整體搬運(yùn)到 rootfs。--enable-static --disable-shared是上一節(jié)確定的庫策略只生成靜態(tài)庫。CFLAGS 里的-marcharmv7-a指定 ARM 架構(gòu)級別Cortex-A7 屬于 ARMv7-A-mfloat-abihard強(qiáng)調(diào)硬浮點(diǎn) ABI。如果你的工具鏈默認(rèn)已經(jīng)按 armv7-a/hard 配置過這兩項可以省略但手動指定能讓 config.log 里的探測結(jié)果更明確。參數(shù)對照表如下參數(shù)本例取值作用--hostarm-linux-gnueabihf聲明目標(biāo)運(yùn)行平臺觸發(fā)交叉編譯探測--buildx86_64-pc-linux-gnu聲明編譯機(jī)平臺防止探測沖突--prefix/opt/imx6ull/libmodbus設(shè)置安裝目錄配合 DESTDIR 重定向--enable-static無附加值生成 libmodbus.a--disable-shared無附加值跳過 libmodbus.so 生成CFLAGS見上方命令編譯期架構(gòu)和浮點(diǎn)參數(shù)3.2 make 編譯與產(chǎn)物確認(rèn)靜態(tài)還是動態(tài)效果差在哪configure 無報錯通過后直接 makemake -j4 file src/.libs/libmodbus.a-j4是并行編譯參數(shù)數(shù)值按你主機(jī)的 CPU 核數(shù)調(diào)整。編譯過程會調(diào)用 libtool把目標(biāo)文件先放進(jìn).libs目錄最終生成的 libmodbus.a 就在那里。file命令是關(guān)鍵檢查點(diǎn)。輸出里必須出現(xiàn)ARM aarch32或ARM字樣比如src/.libs/libmodbus.a: current ar archive, ARM如果這里的輸出是x86-64說明 configure 階段沒生效編譯器還是主機(jī) gcc立刻停下來回頭看 2.1 的 PATH 和 CC 設(shè)置別往下走。靜態(tài)庫和動態(tài)庫在 make 階段的區(qū)別主要體現(xiàn)在目錄內(nèi)容動態(tài)編譯時.libs下還會多出 libmodbus.so.5 以及一串符號鏈接編譯完要把整個.libs目錄都帶走不能只取 libmodbus.so 這一個文件。靜態(tài)編譯則相對干凈只有一個 .a 文件。3.3 make install 后的產(chǎn)物落位頭文件、庫文件和調(diào)用它的 Makefilemake 完成只代表庫編出來了頭文件還沒歸位。下一步用 make install 把產(chǎn)物安裝到指定目錄make install DESTDIR/export/rootfs find /export/rootfs/opt/imx6ull/libmodbus -type f | sort這里的 DESTDIR 是“臨時根目錄”的概念。沒有 DESTDIR 時庫會直接裝到/opt/imx6ull/libmodbus加上 DESTDIR 后實際路徑變成/export/rootfs/opt/imx6ull/libmodbus。這樣既不影響主機(jī)系統(tǒng)又能把整個目錄 rsync 到板子 rootfs 里。find 輸出應(yīng)該包括 include/modbus/modbus.h、include/modbus/modbus-rtu.h、lib/libmodbus.a 這幾個關(guān)鍵文件。后面寫自己項目時Makefile 這樣引用CC arm-linux-gnueabihf-gcc CFLAGS -O2 -I/export/rootfs/opt/imx6ull/libmodbus/include LDFLAGS -L/export/rootfs/opt/imx6ull/libmodbus/lib LIBS -lmodbus -lpthread modbus_demo: main.c $(CC) $(CFLAGS) $(LDFLAGS) -o $ main.c $(LIBS)注意-lmodbus必須放在源文件 main.c 之后。GNU 鏈接器按從左到右的順序解析符號庫放前面時會發(fā)現(xiàn)符號還未被引用導(dǎo)致cannot find -lmodbus這種莫名其妙的報錯。加-lpthread是因為 libmodbus 內(nèi)部用到了線程相關(guān)函數(shù)3.1.6 版在部分工具鏈下不顯式鏈接 pthread 會報 undefined reference。4. 交叉編譯避坑排查configure、鏈接、運(yùn)行三階段四個典型坑環(huán)境類問題一共就三類configure 階段探測失敗、make 階段文件格式?jīng)_突、板子運(yùn)行階段動態(tài)庫或權(quán)限出錯。下面按我實際踩過的順序?qū)懨恳粭l都按“現(xiàn)象、原因、解決”的套路來直接對著查就行。4.1 configure 報錯“C compiler cannot create executables”現(xiàn)象checking whether the C compiler works... no configure: error: C compiler cannot create executables原因最常發(fā)生在剛打開一個新終端時PATH 里沒有交叉工具鏈路徑或者上一個項目在環(huán)境變量里殘留了 x86 的 CC。configure 拿系統(tǒng)默認(rèn) gcc 去“試編譯”編出來的程序是 x86 的隨后它又要用這個程序探測 ARM 環(huán)境自然失敗。解決把 PATH 和 CC 重新對齊然后強(qiáng)制指定編譯器再跑export PATH/opt/arm-linux-gnueabihf/bin:$PATH unset CC ./configure --hostarm-linux-gnueabihf CCarm-linux-gnueabihf-gcc如果還報同樣的錯打開 config.log 看最后 30 行里面記錄著失敗的具體命令和鏈接器報錯。常見的是“cannot find -lc”說明工具鏈不完整需要換一個完整版交叉編譯器問題不在 libmodbus。4.2 make 時報“Skipping incompatible”或“File in wrong format”現(xiàn)象arm-linux-gnueabihf-ld: skipping incompatible /usr/lib/gcc/x86_64-linux-gnu/9/libgcc.a arm-linux-gnueabihf-ld: cannot find -lmodbus原因這是我第一次移植時踩的最深的坑。源碼樹之前在 x86 環(huán)境跑過一次 configureMakefile 里已經(jīng)寫死了主機(jī) gcc 的路徑和一堆 x86 目標(biāo)文件。第二次我只是改了 CC 環(huán)境變量就重新 make舊的目標(biāo)文件還在鏈接器同時收到 ARM 和 x86 兩種格式的輸入直接翻車。解決凡是換工具鏈、換目標(biāo)平臺先徹底清理再重新 configuremake distclean ./configure \ --hostarm-linux-gnueabihf \ --prefix/opt/imx6ull/libmodbus \ --enable-static make -j4make distclean會刪除 Makefile、config.status、所有 .o 文件把源碼樹還原到剛解壓的狀態(tài)。比手動刪rm -rf src/.libs干凈得多。如果 distclean 之后還有殘留最保險的辦法是重新解壓一次源碼包。4.3 拷到板子上運(yùn)行報“Illegal instruction”或“找不到共享庫”現(xiàn)象./modbus_demo: error while loading shared libraries: libmodbus.so.5: cannot open shared object file: No such file or directory Illegal instruction原因分兩類。找不到共享庫說明當(dāng)初用動態(tài)編譯libmodbus.so 沒有拷貝到板子的 /usr/lib 或應(yīng)用同目錄Illegal instruction 則是把別的 ARM 板子上編譯好的 .so 直接拿過來用CPU 架構(gòu)或浮點(diǎn)特性不匹配。解決先確認(rèn)庫里編出來的機(jī)器架構(gòu)arm-linux-gnueabihf-readelf -h src/.libs/libmodbus.so.5 | grep -E Class|Machine輸出 Machine 應(yīng)為 ARM。如果手頭是動態(tài)庫方案把所有 .so 拷到板子后執(zhí)行l(wèi)dconfig刷新緩存如果實在找不到匹配的動態(tài)庫后端恢復(fù)手段就是退回靜態(tài)編譯把--enable-static --disable-shared重新加上編譯出來的 demo 不依賴任何板端 .so直接能跑。4.4 modbus_connect 返回 -1串口權(quán)限和設(shè)備節(jié)點(diǎn)不對現(xiàn)象modbus_new_rtu: ok modbus_connect: Failed to open /dev/ttymxc1: Permission denied原因板子上程序以普通用戶運(yùn)行沒有串口設(shè)備節(jié)點(diǎn)的讀寫權(quán)限或者設(shè)備節(jié)點(diǎn)名本身不對程序打開了不存在的 /dev/ttymxc3報錯會變成 No such file or directory。解決開發(fā)階段直接放開權(quán)限產(chǎn)品階段再用 udev 規(guī)則管理ls -l /dev/ttymxc* sudo chmod 666 /dev/ttymxc1 sudo adduser $USER dialout另外在程序里打開modbus_set_debug(ctx, 1)libmodbus 會把打開串口、設(shè)置波特率每一步都打印出來是排查這類問題最直接的手段。debug 打開后連 -1 都不報說明 device 參數(shù)傳入就有問題優(yōu)先檢查節(jié)點(diǎn)名。5. 裝完怎么驗證跑通在 ARM 板子上用 libmodbus 讀回一個保持寄存器驗證跑通的方式很多我最常用的是寫一個最小讀寄存器程序靜態(tài)編譯后丟到板子上能從 Modbus 從站讀回數(shù)據(jù)整個移植鏈路就算閉環(huán)了。5.1 寫一個最小讀寄存器 Demo#include stdio.h #include errno.h #include modbus/modbus.h int main(void) { modbus_t *ctx; uint16_t reg[2] {0, 0}; ctx modbus_new_rtu(/dev/ttymxc1, 9600, N, 8, 1); if (ctx NULL) { fprintf(stderr, modbus_new_rtu: %s\n, modbus_strerror(errno)); return 1; } modbus_set_debug(ctx, 1); modbus_set_slave(ctx, 1); if (modbus_connect(ctx) -1) { fprintf(stderr, modbus_connect: %s\n, modbus_strerror(errno)); modbus_free(ctx); return 1; } if (modbus_read_registers(ctx, 0, 2, reg) -1) { fprintf(stderr, modbus_read_registers: %s\n, modbus_strerror(errno)); } else { printf(addr00x%04X addr10x%04X\n, reg[0], reg[1]); } modbus_close(ctx); modbus_free(ctx); return 0; }這個 demo 覆蓋了 libmodbus 最核心的四個 API新建 RTU 上下文、設(shè)置從站地址、連接串口、讀保持寄存器。寫的地址是 0讀 2 個寄存器對應(yīng) Modbus 功能碼 03。編譯命令arm-linux-gnueabihf-gcc -o modbus_read_demo main.c \ -I/export/rootfs/opt/imx6ull/libmodbus/include \ -L/export/rootfs/opt/imx6ull/libmodbus/lib \ -lmodbus -lpthread -static加了-static整個二進(jìn)制不依賴 libmodbus.so??降桨遄由蟘p modbus_read_demo /srv/nfs/rootfs/root/ ssh root板子IP chmod x /root/modbus_read_demo /root/modbus_read_demo5.2 板端聯(lián)調(diào)驗證步驟與預(yù)期結(jié)果把 modbus_read_demo 的串口接到一個 Modbus 從站設(shè)備上從站地址設(shè)為 1保持寄存器 0 和 1 里隨便放兩個已知值。運(yùn)行 demo 后由于開了modbus_set_debug串口上會打印類似Query: 01 03 00 00 00 02的報文隨后打印從站返回的數(shù)據(jù)。如果 debug 打印正常但數(shù)據(jù)不對檢查從站地址和寄存器地址是否匹配如果設(shè)備節(jié)點(diǎn)打開失敗回到 4.4 排查權(quán)限如果程序直接 Illegal instruction回到 4.3 檢查架構(gòu)。從那以后我在給 ARM 項目移植 libmodbus 時都會先編一個靜態(tài)讀寄存器 demo 塞進(jìn) rootfs跑通了再動上層業(yè)務(wù)代碼。這個習(xí)慣幫我省掉過好幾個加班的深夜希望這篇記錄也能幫到你。本文還有配套的精品資源點(diǎn)擊獲取