核編譯與IGH EtherCAT主站實(shí)戰(zhàn):1ms周期調(diào)優(yōu))
1. 項(xiàng)目緣起與整體設(shè)計(jì)思路1.1 為什么要在實(shí)時(shí)內(nèi)核上折騰EtherCAT主站做過運(yùn)動(dòng)控制的朋友都知道EtherCAT這套總線協(xié)議在工業(yè)現(xiàn)場有多能打——微秒級的循環(huán)周期、納秒級的同步抖動(dòng)、一根網(wǎng)線串幾十個(gè)從站這些特性讓它在多軸聯(lián)動(dòng)、半導(dǎo)體設(shè)備、精密加工這些場景里幾乎是標(biāo)配。但問題也隨之而來EtherCAT主站對實(shí)時(shí)性的要求極其苛刻普通Linux內(nèi)核那種看心情調(diào)度的方式根本扛不住一個(gè)網(wǎng)絡(luò)中斷處理稍微延遲個(gè)幾百微秒整個(gè)控制環(huán)路就可能抖得不成樣子。我這次的項(xiàng)目目標(biāo)很明確在Debian 13上用打了RT補(bǔ)丁的內(nèi)核把IGH EtherCAT主站跑起來并且要能穩(wěn)定支撐1ms甚至更短的循環(huán)周期。選Debian 13是因?yàn)樗銐蛐聝?nèi)核版本和工具鏈都比較現(xiàn)代省得在老舊發(fā)行版上跟各種依賴打架選RT內(nèi)核是因?yàn)镋therCAT主站的周期性任務(wù)必須搶占一切非關(guān)鍵任務(wù)沒有實(shí)時(shí)搶占能力抖動(dòng)根本壓不下去選IGH EtherCAT是因?yàn)樗情_源方案里最成熟、社區(qū)最活躍的一個(gè)文檔和踩坑記錄都相對豐富。這套組合適合誰參考如果你正在做工業(yè)控制器的原型開發(fā)、機(jī)器人關(guān)節(jié)伺服、或者任何需要硬實(shí)時(shí)以太網(wǎng)通信的項(xiàng)目又不想被商業(yè)主站授權(quán)費(fèi)卡脖子那這套方案基本就是你的菜。當(dāng)然前提是你得對Linux內(nèi)核編譯、網(wǎng)絡(luò)驅(qū)動(dòng)、實(shí)時(shí)調(diào)度這些概念有一定了解純小白直接上手會(huì)有點(diǎn)吃力但我會(huì)盡量把每個(gè)環(huán)節(jié)講透。1.2 整體架構(gòu)與關(guān)鍵選型考量整個(gè)系統(tǒng)的架構(gòu)其實(shí)不復(fù)雜從上到下大致分四層最上面是應(yīng)用層的控制邏輯比如你寫的軌跡規(guī)劃、插補(bǔ)算法中間是IGH EtherCAT主站的用戶態(tài)庫和內(nèi)核態(tài)模塊再往下是RT內(nèi)核提供的實(shí)時(shí)調(diào)度和網(wǎng)絡(luò)協(xié)議棧最底層是網(wǎng)卡驅(qū)動(dòng)和物理網(wǎng)卡。關(guān)鍵選型上我做了幾個(gè)取舍。第一內(nèi)核版本選的是Debian 13默認(rèn)的6.x系列然后打上RT補(bǔ)丁而不是自己從kernel.org拉最新版。原因很簡單Debian的內(nèi)核配置和驅(qū)動(dòng)支持已經(jīng)調(diào)得比較穩(wěn)自己從頭搞容易在網(wǎng)卡驅(qū)動(dòng)上翻車。第二EtherCAT主站用IGH的穩(wěn)定版不追最新開發(fā)版因?yàn)楣I(yè)場景下穩(wěn)定性比新特性重要得多。第三網(wǎng)卡選的是Intel的千兆網(wǎng)卡具體型號就不說了反正就是那種支持DMA、中斷親和性設(shè)置比較靈活的型號Realtek的某些網(wǎng)卡在EtherCAT場景下表現(xiàn)不太行這個(gè)后面會(huì)細(xì)說。還有一個(gè)容易被忽略的點(diǎn)BIOS設(shè)置。我見過太多人內(nèi)核編譯得沒問題結(jié)果BIOS里C-State和P-State沒關(guān)CPU頻率亂跳抖動(dòng)直接爆炸。這個(gè)后面實(shí)操部分會(huì)專門講。2. 環(huán)境準(zhǔn)備與RT內(nèi)核編譯實(shí)操2.1 Debian 13基礎(chǔ)系統(tǒng)安裝與必要依賴先裝一個(gè)干凈的Debian 13最小化安裝就行桌面環(huán)境不需要反而會(huì)引入一堆干擾實(shí)時(shí)性的服務(wù)。安裝的時(shí)候記得把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這里有幾個(gè)包值得說一下。dwarves是給內(nèi)核BTF信息用的新內(nèi)核編譯時(shí)如果缺了它可能會(huì)報(bào)錯(cuò)zstd是用來壓縮內(nèi)核模塊的Debian 13默認(rèn)用zstd壓縮不裝的話編譯到最后會(huì)卡住。libssl-dev和libelf-dev是編譯內(nèi)核模塊簽名和ELF解析用的缺一不可。注意如果你用的是虛擬機(jī)做開發(fā)建議直接跳過EtherCAT對網(wǎng)絡(luò)硬件的依賴太強(qiáng)虛擬機(jī)里的虛擬網(wǎng)卡根本沒法做精確的時(shí)鐘同步。必須用物理機(jī)而且最好是有獨(dú)立網(wǎng)卡的工控機(jī)或服務(wù)器。2.2 獲取RT補(bǔ)丁并編譯內(nèi)核Debian 13的內(nèi)核源碼可以通過apt source linux獲取但更推薦直接從kernel.org下載對應(yīng)版本的源碼然后打RT補(bǔ)丁。我這次用的是6.6系列的長期支持版RT補(bǔ)丁在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補(bǔ)丁打完之后配置內(nèi)核是關(guān)鍵。我建議基于Debian自帶的config來改而不是從defconfig從頭配。把/boot/config-$(uname -r)拷到源碼目錄下重命名為.config然后make olddefconfig再進(jìn)make menuconfig微調(diào)。必須確認(rèn)的幾個(gè)配置項(xiàng)配置項(xiàng)值說明CONFIG_PREEMPT_RTy實(shí)時(shí)搶占核心開關(guān)CONFIG_HZ_1000y時(shí)鐘頻率設(shè)到1000HzCONFIG_HZ1000同上CONFIG_NO_HZ_FULLy無滴答模式減少干擾CONFIG_HIGH_RES_TIMERSy高精度定時(shí)器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ū)動(dòng)按實(shí)際網(wǎng)卡選編譯命令就常規(guī)的make -j$(nproc)然后make modules_install、make install。編譯時(shí)間取決于機(jī)器性能一般十幾到幾十分鐘。編譯完之后更新grub重啟進(jìn)新內(nèi)核用uname -a確認(rèn)帶-rt后綴。實(shí)操心得第一次編譯建議保留Debian原內(nèi)核作為備用啟動(dòng)項(xiàng)萬一新內(nèi)核起不來還能救回來。我就在網(wǎng)卡驅(qū)動(dòng)上翻過一次車新內(nèi)核認(rèn)不到網(wǎng)卡幸好有備用內(nèi)核。2.3 實(shí)時(shí)性驗(yàn)證與基礎(chǔ)調(diào)優(yōu)進(jìn)新內(nèi)核之后先別急著裝EtherCAT得先確認(rèn)實(shí)時(shí)性到底行不行。最常用的工具是cyclictest來自rt-tests包。sudo apt install rt-tests sudo cyclictest -t -p 80 -n -i 10000 -l 100000這個(gè)命令會(huì)起一個(gè)線程優(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)卡中斷號把中斷綁到和實(shí)時(shí)線程不同的核上。電源管理BIOS里關(guān)掉C-State、P-State、SpeedStep這些。內(nèi)核線程ps -eLo pid,tid,cls,rtprio,comm看看有沒有不該跑的高優(yōu)先級線程。我實(shí)測下來一臺配置得當(dāng)?shù)墓た貦C(jī)cyclictest最大延遲能壓到20微秒以內(nèi)這個(gè)水平跑1ms周期的EtherCAT綽綽有余。3. IGH EtherCAT主站編譯與配置3.1 源碼獲取與編譯選項(xiàng)IGH EtherCAT主站的源碼可以從其官方倉庫獲取我這里用git clone的方式方便切換版本。git clone https://gitlab.com/etherlab.org/ethercat.git cd ethercat git checkout stable-1.5 ./bootstrapbootstrap會(huì)生成configure腳本然后就是配置。這里有幾個(gè)關(guān)鍵選項(xiàng)./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ū)動(dòng)模式--enable-igb是針對Intel千兆網(wǎng)卡的優(yōu)化驅(qū)動(dòng)。如果你不確定網(wǎng)卡型號先用generic模式跑通再換專用驅(qū)動(dòng)。--disable-8139too是因?yàn)槟莻€(gè)老驅(qū)動(dòng)基本沒人用了開著反而可能沖突。編譯安裝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)卡綁定與主站啟動(dòng)EtherCAT主站要獨(dú)占一塊網(wǎng)卡所以得先把網(wǎng)卡從內(nèi)核標(biāo)準(zhǔn)驅(qū)動(dòng)上解綁交給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腳本會(huì)自動(dòng)處理網(wǎng)卡綁定。如果啟動(dòng)失敗檢查dmesg里的報(bào)錯(cuò)常見的是網(wǎng)卡被NetworkManager占用了需要把/etc/NetworkManager/NetworkManager.conf里的managedfalse加上或者直接停掉NetworkManager。啟動(dòng)成功后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正常的話會(huì)列出每個(gè)從站的型號、位置、狀態(tài)。如果從站顯示PREOP而不是OP說明PDO配置還沒做。PDO配置有兩種方式一種是直接用ethercat cstruct導(dǎo)出從站的XML描述另一種是手動(dòng)寫配置。我推薦前者省事且不容易出錯(cuò)。ethercat cstruct -p 0 slave0.xml拿到XML后用ethercat pdos查看可用的PDO映射然后在應(yīng)用層配置。IGH的用戶態(tài)庫libethercat提供了完整的APIC語言接口用起來還算順手。注意不同廠商的從站XML格式可能有差異有些從站的ESI文件里PDO定義不全需要手動(dòng)補(bǔ)。我遇到過一個(gè)伺服從站廠商給的XML里少了DC同步的配置導(dǎo)致多軸同步時(shí)抖動(dòng)特別大后來手動(dòng)加了DC節(jié)點(diǎn)才解決。4. 實(shí)時(shí)性能調(diào)優(yōu)與常見問題排查4.1 中斷親和性與CPU隔離EtherCAT主站的實(shí)時(shí)線程和網(wǎng)卡中斷如果跑在同一個(gè)核上會(huì)互相干擾。我的做法是把CPU核分三組核0跑系統(tǒng)和非實(shí)時(shí)任務(wù)核1跑EtherCAT實(shí)時(shí)線程核2跑網(wǎng)卡中斷。隔離CPU核可以在內(nèi)核啟動(dòng)參數(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。實(shí)時(shí)線程的親和性在應(yīng)用層用pthread_setaffinity_np設(shè)置。4.2 常見問題速查表現(xiàn)象可能原因排查方法解決方案主站啟動(dòng)失敗網(wǎng)卡被占用dmesg看報(bào)錯(cuò)停NetworkManager或加managedfalse從站掃描不到網(wǎng)線或從站供電換網(wǎng)線、查從站指示燈檢查物理連接從站卡在PREOPPDO未配置ethercat pdos配置PDO映射循環(huán)周期抖動(dòng)大CPU頻率調(diào)節(jié)cpupower frequency-info改performance模式DC同步失敗從站不支持或配置錯(cuò)ethercat dc檢查從站DC能力編譯報(bào)錯(cuò)缺頭文件依賴沒裝全看報(bào)錯(cuò)信息補(bǔ)裝對應(yīng)dev包4.3 獨(dú)家避坑經(jīng)驗(yàn)第一個(gè)坑網(wǎng)卡選型。我一開始用了一塊Realtek的網(wǎng)卡generic驅(qū)動(dòng)能跑但抖動(dòng)特別大后來換成Intel的立馬就好了。原因是Realtek的驅(qū)動(dòng)在中斷處理上不夠高效而且DMA描述符管理有缺陷。所以如果你要做高精度控制網(wǎng)卡別省那點(diǎn)錢。第二個(gè)坑BIOS里的C-State。這個(gè)我踩過兩次一次是工控機(jī)一次是服務(wù)器。BIOS里C-State沒關(guān)CPU在空閑時(shí)進(jìn)深度睡眠喚醒延遲直接導(dǎo)致EtherCAT周期抖動(dòng)超過200微秒。關(guān)掉之后立馬降到30微秒以內(nèi)。第三個(gè)坑內(nèi)核版本匹配。RT補(bǔ)丁和內(nèi)核版本必須嚴(yán)格對應(yīng)6.6.10的補(bǔ)丁不能打到6.6.11上哪怕小版本號差一位也不行。我試過一次偷懶結(jié)果編譯報(bào)了一堆錯(cuò)老老實(shí)實(shí)下對應(yīng)版本才過。第四個(gè)坑從站ESI文件。有些從站的ESI文件里PDO映射和實(shí)際不符尤其是國產(chǎn)從站。這時(shí)候需要用ethercat cstruct導(dǎo)出實(shí)際配置對比ESI文件手動(dòng)修正。我遇到過一個(gè)案例ESI里寫的是16位PDO實(shí)際是32位導(dǎo)致數(shù)據(jù)解析全錯(cuò)。5. 應(yīng)用層開發(fā)與周期任務(wù)實(shí)現(xiàn)5.1 用戶態(tài)實(shí)時(shí)線程編寫IGH主站提供用戶態(tài)庫應(yīng)用層可以直接調(diào)用。核心是創(chuàng)建一個(gè)實(shí)時(shí)線程在循環(huán)里調(diào)用ecrt_master_receive、ecrt_domain_process、ecrt_domain_queue、ecrt_master_send這四個(gè)函數(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的絕對時(shí)間模式而不是相對時(shí)間是為了避免累積誤差。每次循環(huán)基于上一次的目標(biāo)時(shí)間加1ms這樣即使某次循環(huán)延遲了下一次也會(huì)自動(dòng)補(bǔ)償回來。5.2 分布式時(shí)鐘同步配置多軸聯(lián)動(dòng)場景下DC同步是必須的。配置DC需要在從站激活階段設(shè)置參考時(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)該在納秒級。實(shí)操心得DC同步剛配好的時(shí)候偏移量可能比較大需要跑一段時(shí)間讓從站自己收斂。如果一直不收斂檢查從站是否支持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ā)里最容易出錯(cuò)的地方。每個(gè)從站的PDO條目都有特定的數(shù)據(jù)類型和位偏移映射的時(shí)候必須嚴(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時(shí)確定的必須和從站ESI文件里的定義一致。如果讀出來的數(shù)據(jù)明顯不對比如位置值一直在跳變或者超出量程大概率是偏移量算錯(cuò)了。6. 系統(tǒng)集成與長期運(yùn)行穩(wěn)定性6.1 開機(jī)自啟動(dòng)配置生產(chǎn)環(huán)境不可能每次手動(dòng)啟動(dòng)主站得配成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因?yàn)閑thercatctl是后臺運(yùn)行的。另外Restarton-failure保證主站崩潰后能自動(dòng)拉起。6.2 長時(shí)間運(yùn)行監(jiān)控EtherCAT主站跑起來容易跑穩(wěn)難。我一般會(huì)加幾個(gè)監(jiān)控點(diǎn)一是ethercat master的相位狀態(tài)二是ethercat slaves的從站狀態(tài)三是應(yīng)用層的循環(huán)周期抖動(dòng)統(tǒng)計(jì)。循環(huán)周期抖動(dòng)可以在應(yīng)用層自己統(tǒng)計(jì)每次循環(huán)記錄實(shí)際時(shí)間和目標(biāo)時(shí)間的差值跑一段時(shí)間后算最大值、平均值、標(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)計(jì)diff的分布6.3 擴(kuò)展方向與個(gè)人體會(huì)這套方案跑通之后可以往幾個(gè)方向擴(kuò)展。一是加冗余網(wǎng)卡主站支持雙網(wǎng)卡冗余一根線斷了自動(dòng)切換二是集成到ROS2或者其他的機(jī)器人框架里做上層運(yùn)動(dòng)規(guī)劃三是加實(shí)時(shí)日志和診斷方便現(xiàn)場調(diào)試。我個(gè)人在實(shí)際操作中的體會(huì)是EtherCAT主站開發(fā)最難的不是寫代碼而是調(diào)系統(tǒng)。內(nèi)核配置、BIOS設(shè)置、網(wǎng)卡選型、中斷親和性這些底層的東西沒調(diào)好上層代碼寫得再漂亮也白搭。我建議新手先從generic驅(qū)動(dòng)跑通再逐步優(yōu)化到專用驅(qū)動(dòng)和DC同步不要一上來就追求極致性能容易在細(xì)節(jié)里迷失。最后再分享一個(gè)小技巧如果你在調(diào)試過程中發(fā)現(xiàn)從站偶爾掉線先別懷疑代碼用ethercat slaves看從站狀態(tài)再用dmesg看內(nèi)核日志。十有八九是網(wǎng)線接觸不良或者從站供電不穩(wěn)硬件問題比軟件問題更常見。