核編譯與IGH EtherCAT主站實戰(zhàn):1ms周期調(diào)優(yōu))
1. 項目緣起與整體設(shè)計思路1.1 為什么要在實時內(nèi)核上折騰EtherCAT主站做過運動控制的朋友都知道EtherCAT這套總線協(xié)議在工業(yè)現(xiàn)場有多能打——微秒級的循環(huán)周期、納秒級的同步抖動、一根網(wǎng)線串幾十個從站這些特性讓它在多軸聯(lián)動、半導(dǎo)體設(shè)備、精密加工這些場景里幾乎是標(biāo)配。但問題也隨之而來EtherCAT主站對實時性的要求極其苛刻普通Linux內(nèi)核那種看心情調(diào)度的方式根本扛不住一個網(wǎng)絡(luò)中斷處理稍微延遲個幾百微秒整個控制環(huán)路就可能抖得不成樣子。我這次的項目目標(biāo)很明確在Debian 13上用打了RT補丁的內(nèi)核把IGH EtherCAT主站跑起來并且要能穩(wěn)定支撐1ms甚至更短的循環(huán)周期。選Debian 13是因為它足夠新內(nèi)核版本和工具鏈都比較現(xiàn)代省得在老舊發(fā)行版上跟各種依賴打架選RT內(nèi)核是因為EtherCAT主站的周期性任務(wù)必須搶占一切非關(guān)鍵任務(wù)沒有實時搶占能力抖動根本壓不下去選IGH EtherCAT是因為它是開源方案里最成熟、社區(qū)最活躍的一個文檔和踩坑記錄都相對豐富。這套組合適合誰參考如果你正在做工業(yè)控制器的原型開發(fā)、機(jī)器人關(guān)節(jié)伺服、或者任何需要硬實時以太網(wǎng)通信的項目又不想被商業(yè)主站授權(quán)費卡脖子那這套方案基本就是你的菜。當(dāng)然前提是你得對Linux內(nèi)核編譯、網(wǎng)絡(luò)驅(qū)動、實時調(diào)度這些概念有一定了解純小白直接上手會有點吃力但我會盡量把每個環(huán)節(jié)講透。1.2 整體架構(gòu)與關(guān)鍵選型考量整個系統(tǒng)的架構(gòu)其實不復(fù)雜從上到下大致分四層最上面是應(yīng)用層的控制邏輯比如你寫的軌跡規(guī)劃、插補算法中間是IGH EtherCAT主站的用戶態(tài)庫和內(nèi)核態(tài)模塊再往下是RT內(nèi)核提供的實時調(diào)度和網(wǎng)絡(luò)協(xié)議棧最底層是網(wǎng)卡驅(qū)動和物理網(wǎng)卡。關(guān)鍵選型上我做了幾個取舍。第一內(nèi)核版本選的是Debian 13默認(rèn)的6.x系列然后打上RT補丁而不是自己從kernel.org拉最新版。原因很簡單Debian的內(nèi)核配置和驅(qū)動支持已經(jīng)調(diào)得比較穩(wěn)自己從頭搞容易在網(wǎng)卡驅(qū)動上翻車。第二EtherCAT主站用IGH的穩(wěn)定版不追最新開發(fā)版因為工業(yè)場景下穩(wěn)定性比新特性重要得多。第三網(wǎng)卡選的是Intel的千兆網(wǎng)卡具體型號就不說了反正就是那種支持DMA、中斷親和性設(shè)置比較靈活的型號Realtek的某些網(wǎng)卡在EtherCAT場景下表現(xiàn)不太行這個后面會細(xì)說。還有一個容易被忽略的點BIOS設(shè)置。我見過太多人內(nèi)核編譯得沒問題結(jié)果BIOS里C-State和P-State沒關(guān)CPU頻率亂跳抖動直接爆炸。這個后面實操部分會專門講。2. 環(huán)境準(zhǔn)備與RT內(nèi)核編譯實操2.1 Debian 13基礎(chǔ)系統(tǒng)安裝與必要依賴先裝一個干凈的Debian 13最小化安裝就行桌面環(huán)境不需要反而會引入一堆干擾實時性的服務(wù)。安裝的時候記得把SSH服務(wù)勾上后面方便遠(yuǎn)程操作。裝完之后第一件事是換源、更新、裝編譯依賴。sudo apt update sudo apt install -y build-essential kernel-package libncurses-dev \ bison flex libssl-dev libelf-dev bc rsync kmod cpio \ git wget curl dwarves zstd這里有幾個包值得說一下。dwarves是給內(nèi)核BTF信息用的新內(nèi)核編譯時如果缺了它可能會報錯zstd是用來壓縮內(nèi)核模塊的Debian 13默認(rèn)用zstd壓縮不裝的話編譯到最后會卡住。libssl-dev和libelf-dev是編譯內(nèi)核模塊簽名和ELF解析用的缺一不可。注意如果你用的是虛擬機(jī)做開發(fā)建議直接跳過EtherCAT對網(wǎng)絡(luò)硬件的依賴太強虛擬機(jī)里的虛擬網(wǎng)卡根本沒法做精確的時鐘同步。必須用物理機(jī)而且最好是有獨立網(wǎng)卡的工控機(jī)或服務(wù)器。2.2 獲取RT補丁并編譯內(nèi)核Debian 13的內(nèi)核源碼可以通過apt source linux獲取但更推薦直接從kernel.org下載對應(yīng)版本的源碼然后打RT補丁。我這次用的是6.6系列的長期支持版RT補丁在kernel.org的rt/目錄下能找到對應(yīng)版本。wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.xx.tar.xz wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/6.6/patch-6.6.xx-rtxx.patch.xz tar -xf linux-6.6.xx.tar.xz cd linux-6.6.xx xz -d ../patch-6.6.xx-rtxx.patch.xz patch -p1 ../patch-6.6.xx-rtxx.patch補丁打完之后配置內(nèi)核是關(guān)鍵。我建議基于Debian自帶的config來改而不是從defconfig從頭配。把/boot/config-$(uname -r)拷到源碼目錄下重命名為.config然后make olddefconfig再進(jìn)make menuconfig微調(diào)。必須確認(rèn)的幾個配置項配置項值說明CONFIG_PREEMPT_RTy實時搶占核心開關(guān)CONFIG_HZ_1000y時鐘頻率設(shè)到1000HzCONFIG_HZ1000同上CONFIG_NO_HZ_FULLy無滴答模式減少干擾CONFIG_HIGH_RES_TIMERSy高精度定時器CONFIG_CPU_FREQn關(guān)掉頻率調(diào)節(jié)防止降頻CONFIG_CPU_IDLEn關(guān)掉CPU空閑狀態(tài)CONFIG_DEBUG_PREEMPTn生產(chǎn)環(huán)境關(guān)掉調(diào)試CONFIG_IGByIntel千兆網(wǎng)卡驅(qū)動按實際網(wǎng)卡選編譯命令就常規(guī)的make -j$(nproc)然后make modules_install、make install。編譯時間取決于機(jī)器性能一般十幾到幾十分鐘。編譯完之后更新grub重啟進(jìn)新內(nèi)核用uname -a確認(rèn)帶-rt后綴。實操心得第一次編譯建議保留Debian原內(nèi)核作為備用啟動項萬一新內(nèi)核起不來還能救回來。我就在網(wǎng)卡驅(qū)動上翻過一次車新內(nèi)核認(rèn)不到網(wǎng)卡幸好有備用內(nèi)核。2.3 實時性驗證與基礎(chǔ)調(diào)優(yōu)進(jìn)新內(nèi)核之后先別急著裝EtherCAT得先確認(rèn)實時性到底行不行。最常用的工具是cyclictest來自rt-tests包。sudo apt install rt-tests sudo cyclictest -t -p 80 -n -i 10000 -l 100000這個命令會起一個線程優(yōu)先級80每10ms喚醒一次跑10萬次最后輸出最大延遲。理想情況下最大延遲應(yīng)該在幾十微秒以內(nèi)如果超過100微秒說明系統(tǒng)里還有干擾源。常見的干擾源和排查方法CPU頻率調(diào)節(jié)cpupower frequency-info看看是不是在ondemand模式改成performance。中斷親和性cat /proc/interrupts找到網(wǎng)卡中斷號把中斷綁到和實時線程不同的核上。電源管理BIOS里關(guān)掉C-State、P-State、SpeedStep這些。內(nèi)核線程ps -eLo pid,tid,cls,rtprio,comm看看有沒有不該跑的高優(yōu)先級線程。我實測下來一臺配置得當(dāng)?shù)墓た貦C(jī)cyclictest最大延遲能壓到20微秒以內(nèi)這個水平跑1ms周期的EtherCAT綽綽有余。3. IGH EtherCAT主站編譯與配置3.1 源碼獲取與編譯選項IGH EtherCAT主站的源碼可以從其官方倉庫獲取我這里用git clone的方式方便切換版本。git clone https://gitlab.com/etherlab.org/ethercat.git cd ethercat git checkout stable-1.5 ./bootstrapbootstrap會生成configure腳本然后就是配置。這里有幾個關(guān)鍵選項./configure --prefix/usr/local/etherlab \ --disable-8139too \ --enable-generic \ --enable-igb \ --with-linux-dir/usr/src/linux-6.6.xx--enable-generic是通用網(wǎng)卡驅(qū)動模式--enable-igb是針對Intel千兆網(wǎng)卡的優(yōu)化驅(qū)動。如果你不確定網(wǎng)卡型號先用generic模式跑通再換專用驅(qū)動。--disable-8139too是因為那個老驅(qū)動基本沒人用了開著反而可能沖突。編譯安裝make -j$(nproc) sudo make modules_install sudo make install sudo depmod裝完之后主站的內(nèi)核模塊在/usr/local/etherlab/lib/modules/下用戶態(tài)工具在/usr/local/etherlab/bin/下。3.2 網(wǎng)卡綁定與主站啟動EtherCAT主站要獨占一塊網(wǎng)卡所以得先把網(wǎng)卡從內(nèi)核標(biāo)準(zhǔn)驅(qū)動上解綁交給EtherCAT模塊接管。假設(shè)你的網(wǎng)卡是eth1先看它的PCI地址lspci -v | grep -A 10 Ethernet找到類似0000:03:00.0的地址然后sudo modprobe ec_generic sudo /usr/local/etherlab/bin/ethercatctl startethercatctl腳本會自動處理網(wǎng)卡綁定。如果啟動失敗檢查dmesg里的報錯常見的是網(wǎng)卡被NetworkManager占用了需要把/etc/NetworkManager/NetworkManager.conf里的managedfalse加上或者直接停掉NetworkManager。啟動成功后ethercat master應(yīng)該能看到主站狀態(tài)Master0 Phase: Idle Active: no Slaves: 0 Ethernet devices: Main: 03:00.0 (attached)3.3 從站掃描與PDO配置接上從站設(shè)備后用ethercat slaves掃描ethercat slaves正常的話會列出每個從站的型號、位置、狀態(tài)。如果從站顯示PREOP而不是OP說明PDO配置還沒做。PDO配置有兩種方式一種是直接用ethercat cstruct導(dǎo)出從站的XML描述另一種是手動寫配置。我推薦前者省事且不容易出錯。ethercat cstruct -p 0 slave0.xml拿到XML后用ethercat pdos查看可用的PDO映射然后在應(yīng)用層配置。IGH的用戶態(tài)庫libethercat提供了完整的APIC語言接口用起來還算順手。注意不同廠商的從站XML格式可能有差異有些從站的ESI文件里PDO定義不全需要手動補。我遇到過一個伺服從站廠商給的XML里少了DC同步的配置導(dǎo)致多軸同步時抖動特別大后來手動加了DC節(jié)點才解決。4. 實時性能調(diào)優(yōu)與常見問題排查4.1 中斷親和性與CPU隔離EtherCAT主站的實時線程和網(wǎng)卡中斷如果跑在同一個核上會互相干擾。我的做法是把CPU核分三組核0跑系統(tǒng)和非實時任務(wù)核1跑EtherCAT實時線程核2跑網(wǎng)卡中斷。隔離CPU核可以在內(nèi)核啟動參數(shù)里加isolcpus1,2 nohz_full1,2 rcu_nocbs1,2。然后設(shè)置中斷親和性echo 4 /proc/irq/$(cat /proc/interrupts | grep eth1 | awk {print $1} | tr -d :)/smp_affinity這里的4是十六進(jìn)制對應(yīng)CPU2。實時線程的親和性在應(yīng)用層用pthread_setaffinity_np設(shè)置。4.2 常見問題速查表現(xiàn)象可能原因排查方法解決方案主站啟動失敗網(wǎng)卡被占用dmesg看報錯停NetworkManager或加managedfalse從站掃描不到網(wǎng)線或從站供電換網(wǎng)線、查從站指示燈檢查物理連接從站卡在PREOPPDO未配置ethercat pdos配置PDO映射循環(huán)周期抖動大CPU頻率調(diào)節(jié)cpupower frequency-info改performance模式DC同步失敗從站不支持或配置錯ethercat dc檢查從站DC能力編譯報錯缺頭文件依賴沒裝全看報錯信息補裝對應(yīng)dev包4.3 獨家避坑經(jīng)驗第一個坑網(wǎng)卡選型。我一開始用了一塊Realtek的網(wǎng)卡generic驅(qū)動能跑但抖動特別大后來換成Intel的立馬就好了。原因是Realtek的驅(qū)動在中斷處理上不夠高效而且DMA描述符管理有缺陷。所以如果你要做高精度控制網(wǎng)卡別省那點錢。第二個坑BIOS里的C-State。這個我踩過兩次一次是工控機(jī)一次是服務(wù)器。BIOS里C-State沒關(guān)CPU在空閑時進(jìn)深度睡眠喚醒延遲直接導(dǎo)致EtherCAT周期抖動超過200微秒。關(guān)掉之后立馬降到30微秒以內(nèi)。第三個坑內(nèi)核版本匹配。RT補丁和內(nèi)核版本必須嚴(yán)格對應(yīng)6.6.10的補丁不能打到6.6.11上哪怕小版本號差一位也不行。我試過一次偷懶結(jié)果編譯報了一堆錯老老實實下對應(yīng)版本才過。第四個坑從站ESI文件。有些從站的ESI文件里PDO映射和實際不符尤其是國產(chǎn)從站。這時候需要用ethercat cstruct導(dǎo)出實際配置對比ESI文件手動修正。我遇到過一個案例ESI里寫的是16位PDO實際是32位導(dǎo)致數(shù)據(jù)解析全錯。5. 應(yīng)用層開發(fā)與周期任務(wù)實現(xiàn)5.1 用戶態(tài)實時線程編寫IGH主站提供用戶態(tài)庫應(yīng)用層可以直接調(diào)用。核心是創(chuàng)建一個實時線程在循環(huán)里調(diào)用ecrt_master_receive、ecrt_domain_process、ecrt_domain_queue、ecrt_master_send這四個函數(shù)。#include ecrt.h #include pthread.h #include sched.h void *cyclic_task(void *arg) { struct sched_param param; param.sched_priority 80; pthread_setschedparam(pthread_self(), SCHED_FIFO, param); struct timespec next; clock_gettime(CLOCK_MONOTONIC, next); while (running) { next.tv_nsec 1000000; // 1ms if (next.tv_nsec 1000000000) { next.tv_nsec - 1000000000; next.tv_sec; } clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, next, NULL); ecrt_master_receive(master); ecrt_domain_process(domain); // 你的控制邏輯 ecrt_domain_queue(domain); ecrt_master_send(master); } return NULL; }這里用clock_nanosleep的絕對時間模式而不是相對時間是為了避免累積誤差。每次循環(huán)基于上一次的目標(biāo)時間加1ms這樣即使某次循環(huán)延遲了下一次也會自動補償回來。5.2 分布式時鐘同步配置多軸聯(lián)動場景下DC同步是必須的。配置DC需要在從站激活階段設(shè)置參考時鐘和同步周期ecrt_master_select_reference_clock(master, sc); ecrt_master_application_time(master, app_time); ecrt_slave_config_dc(sc, 0x0300, 1000000, 0, 0, 0);0x0300是DC控制字1000000是同步周期1ms。配置好之后用ethercat dc查看同步狀態(tài)偏移量應(yīng)該在納秒級。實操心得DC同步剛配好的時候偏移量可能比較大需要跑一段時間讓從站自己收斂。如果一直不收斂檢查從站是否支持DC以及網(wǎng)線質(zhì)量是否達(dá)標(biāo)。我遇到過一根劣質(zhì)網(wǎng)線導(dǎo)致DC永遠(yuǎn)同步不上的情況換線就好了。5.3 數(shù)據(jù)映射與類型轉(zhuǎn)換PDO數(shù)據(jù)映射是EtherCAT開發(fā)里最容易出錯的地方。每個從站的PDO條目都有特定的數(shù)據(jù)類型和位偏移映射的時候必須嚴(yán)格對應(yīng)。IGH提供了EC_WRITE_*和EC_READ_*系列宏來處理。uint8_t *domain_pd ecrt_domain_data(domain); int32_t actual_pos EC_READ_S32(domain_pd offset_pos); EC_WRITE_S32(domain_pd offset_cmd, target_pos);偏移量offset_pos和offset_cmd是在配置PDO時確定的必須和從站ESI文件里的定義一致。如果讀出來的數(shù)據(jù)明顯不對比如位置值一直在跳變或者超出量程大概率是偏移量算錯了。6. 系統(tǒng)集成與長期運行穩(wěn)定性6.1 開機(jī)自啟動配置生產(chǎn)環(huán)境不可能每次手動啟動主站得配成systemd服務(wù)。IGH自帶的ethercatctl腳本可以改造成service[Unit] DescriptionEtherCAT Master Afternetwork.target [Service] Typeforking ExecStart/usr/local/etherlab/bin/ethercatctl start ExecStop/usr/local/etherlab/bin/ethercatctl stop Restarton-failure [Install] WantedBymulti-user.target注意Typeforking因為ethercatctl是后臺運行的。另外Restarton-failure保證主站崩潰后能自動拉起。6.2 長時間運行監(jiān)控EtherCAT主站跑起來容易跑穩(wěn)難。我一般會加幾個監(jiān)控點一是ethercat master的相位狀態(tài)二是ethercat slaves的從站狀態(tài)三是應(yīng)用層的循環(huán)周期抖動統(tǒng)計。循環(huán)周期抖動可以在應(yīng)用層自己統(tǒng)計每次循環(huán)記錄實際時間和目標(biāo)時間的差值跑一段時間后算最大值、平均值、標(biāo)準(zhǔn)差。如果標(biāo)準(zhǔn)差超過周期的10%說明系統(tǒng)有干擾需要排查。int64_t diff (actual.tv_sec - next.tv_sec) * 1000000000LL (actual.tv_nsec - next.tv_nsec); // 統(tǒng)計diff的分布6.3 擴(kuò)展方向與個人體會這套方案跑通之后可以往幾個方向擴(kuò)展。一是加冗余網(wǎng)卡主站支持雙網(wǎng)卡冗余一根線斷了自動切換二是集成到ROS2或者其他的機(jī)器人框架里做上層運動規(guī)劃三是加實時日志和診斷方便現(xiàn)場調(diào)試。我個人在實際操作中的體會是EtherCAT主站開發(fā)最難的不是寫代碼而是調(diào)系統(tǒng)。內(nèi)核配置、BIOS設(shè)置、網(wǎng)卡選型、中斷親和性這些底層的東西沒調(diào)好上層代碼寫得再漂亮也白搭。我建議新手先從generic驅(qū)動跑通再逐步優(yōu)化到專用驅(qū)動和DC同步不要一上來就追求極致性能容易在細(xì)節(jié)里迷失。最后再分享一個小技巧如果你在調(diào)試過程中發(fā)現(xiàn)從站偶爾掉線先別懷疑代碼用ethercat slaves看從站狀態(tài)再用dmesg看內(nèi)核日志。十有八九是網(wǎng)線接觸不良或者從站供電不穩(wěn)硬件問題比軟件問題更常見。