戰(zhàn):AXI DMA+LWIP TCP數(shù)據(jù)鏈路搭建指南)
簡(jiǎn)介面向ZYNQ系列FPGA開(kāi)發(fā)者的完整工程資料圍繞PL與PS端AXI通信、LWIP協(xié)議棧移植及網(wǎng)口TCP數(shù)據(jù)傳輸?shù)缴衔粰C(jī)的實(shí)現(xiàn)展開(kāi)適合具備一定嵌入式基礎(chǔ)并希望完成高速網(wǎng)絡(luò)通信設(shè)計(jì)的工程師與學(xué)生。資源包體積約56.6MB內(nèi)容聚焦從架構(gòu)分析、接口選型到調(diào)試驗(yàn)證的完整鏈路可幫助理解AXI4-Lite/Stream在PL與PS交互中的應(yīng)用、DMA數(shù)據(jù)搬運(yùn)機(jī)制以及網(wǎng)口物理層配置思路并提供設(shè)計(jì)挑戰(zhàn)與優(yōu)化方向的實(shí)用參考。目前已有10721人學(xué)習(xí)資源整體結(jié)構(gòu)清晰既可作為初次接觸ZYNQ網(wǎng)口通信的入門(mén)指南也能為實(shí)際項(xiàng)目中PL-PS協(xié)同與TCP傳輸調(diào)試提供直接借鑒。 搞FPGA的兄弟特別是剛開(kāi)始碰ZYNQ的朋友十有八九都會(huì)卡在PL和PS通信這一步。我當(dāng)年從純邏輯器件轉(zhuǎn)到ZYNQ平臺(tái)踩了整整一周的坑才把一條完整的數(shù)據(jù)鏈路跑通PL端采進(jìn)來(lái)的數(shù)據(jù)通過(guò)AXI總線(xiàn)交給PSPS再打包好走網(wǎng)口TCP丟給上位機(jī)。這套架構(gòu)是ZYNQ開(kāi)發(fā)最經(jīng)典也最實(shí)用的數(shù)據(jù)通路圖像采集、高速數(shù)據(jù)記錄、軟件無(wú)線(xiàn)電、工業(yè)控制基本都離不開(kāi)它。這個(gè)項(xiàng)目要解決的問(wèn)題很直接——把“硬件側(cè)實(shí)時(shí)產(chǎn)生/采集到的數(shù)據(jù)”可靠地送到“PC機(jī)上運(yùn)行的應(yīng)用程序里”。有了這條鏈路你才能在上位機(jī)做波形顯示、算法處理、日志存儲(chǔ)。如果你是剛?cè)腴T(mén)ZYNQ想搞明白PL和PS之間怎么高效交互以及PS怎么用網(wǎng)口往外發(fā)數(shù)據(jù)這篇實(shí)戰(zhàn)過(guò)程就是照著干活的那種參考。不是教科書(shū)式的泛泛而談是每一步都實(shí)際跑過(guò)的記錄。1. 方案整體設(shè)計(jì)與核心思路動(dòng)手之前我花了不少時(shí)間想清楚整個(gè)鏈路怎么搭因?yàn)檫@個(gè)方案的選型直接影響后面所有工作量。1.1 數(shù)據(jù)通路的三個(gè)關(guān)鍵環(huán)節(jié)一條完整的PL→PS→TCP→上位機(jī)鏈路拆開(kāi)看是三個(gè)獨(dú)立但耦合的部分PL側(cè)數(shù)據(jù)生產(chǎn)可以是ADC采樣數(shù)據(jù)、圖像傳感器的像素流、或者是內(nèi)部邏輯計(jì)算出來(lái)的結(jié)果。PS側(cè)數(shù)據(jù)搬運(yùn)與協(xié)議封裝PS通過(guò)AXI接口把PL的數(shù)據(jù)搬到DDR內(nèi)存然后加上TCP/IP協(xié)議頭通過(guò)網(wǎng)絡(luò)發(fā)送出去。上位機(jī)數(shù)據(jù)接收與解析PC端用網(wǎng)絡(luò)調(diào)試助手或者自研軟件監(jiān)聽(tīng)端口按約定好的幀格式把數(shù)據(jù)還原出來(lái)。這三個(gè)環(huán)節(jié)難點(diǎn)第一在PL和PS的接口交互第二在PS側(cè)TCP協(xié)議棧的配置第三在數(shù)據(jù)幀格式的設(shè)計(jì)。鏈路越長(zhǎng)越容易出錯(cuò)所以我在設(shè)計(jì)時(shí)堅(jiān)持一個(gè)原則每一層都留出獨(dú)立的調(diào)試觀察點(diǎn)這樣出了問(wèn)題能迅速定位到是數(shù)據(jù)沒(méi)產(chǎn)生、沒(méi)搬走、還是沒(méi)發(fā)出去。1.2 為什么TCP而不是UDP很多人會(huì)問(wèn)實(shí)時(shí)性要求高一點(diǎn)的數(shù)據(jù)是不是用UDP更合適。我的選擇是TCP原因要分場(chǎng)景看可靠性?xún)?yōu)先TCP自帶重傳機(jī)制數(shù)據(jù)丟了一包會(huì)自動(dòng)補(bǔ)發(fā)對(duì)“上位機(jī)必須收到完整數(shù)據(jù)”的場(chǎng)景非常友好能避免很多莫名其妙的數(shù)據(jù)缺損問(wèn)題。調(diào)試方便TCP傳輸?shù)恼{(diào)試工具遍地都是上位機(jī)起一個(gè)Socket綁定端口就能收速度慢了能明顯看出來(lái)排查問(wèn)題的路徑容易被觀察。后期擴(kuò)展如果將來(lái)要接數(shù)據(jù)庫(kù)、云平臺(tái)或者做遠(yuǎn)程服務(wù)TCP的通用性比UDP強(qiáng)得多。代價(jià)就是TCP首部開(kāi)銷(xiāo)比UDP大且需要維護(hù)連接狀態(tài)。但如果你的應(yīng)用以太網(wǎng)帶寬在幾十兆到一百多兆這個(gè)量級(jí)TCP完全撐得住沒(méi)必要去折騰UDP的丟包補(bǔ)償邏輯。1.3 數(shù)據(jù)鏈路方案選型PS端我選擇用裸機(jī)加lwIP協(xié)議棧的方案。有人可能會(huì)問(wèn)為什么不用Linux Socket那個(gè)寫(xiě)起來(lái)不是更簡(jiǎn)單拜熱詞里那些問(wèn)題所賜我專(zhuān)門(mén)對(duì)比過(guò)如果項(xiàng)目里只有純數(shù)據(jù)傳輸需求、沒(méi)有復(fù)雜的文件系統(tǒng)或進(jìn)程管理需求裸機(jī)lwIP的實(shí)時(shí)性和確定性比Linux更好。Linux的調(diào)度開(kāi)銷(xiāo)和驅(qū)動(dòng)復(fù)雜度對(duì)新手非常不友好你一個(gè)socket()調(diào)用卡兩毫秒數(shù)據(jù)流就會(huì)斷層。lwIP在裸機(jī)上跑用戶(hù)態(tài)代碼直接操作協(xié)議棧吞吐和延遲都可控。PL和PS之間我選擇AXI DMA方案沒(méi)有用簡(jiǎn)單的AXI-Lite寄存器輪詢(xún)。因?yàn)槿绻棵胍獋鲙资甅B的數(shù)據(jù)CPU輪詢(xún)搬數(shù)據(jù)既浪費(fèi)時(shí)間又容易丟而AXI DMA可以在硬件層面完成大批量從PL到DDR的搬運(yùn)CPU只需要在傳輸完成后被中斷喚醒就好。這條方案選對(duì)了后面的工作量減少一半以上。2. 硬件工程搭建與AXI總線(xiàn)細(xì)節(jié)這步是驗(yàn)證數(shù)據(jù)鏈路能不能通的關(guān)鍵具體怎么把Vivado工程搭起來(lái)、怎么配置DMA都有講究。2.1 AXI接口的類(lèi)型與選型ZYNQ的PS和PL之間通過(guò)AXI總線(xiàn)通信Xilinx提供三種主要接口我簡(jiǎn)單列一下接口類(lèi)型用途特點(diǎn)AXI-Lite寄存器讀寫(xiě)控制數(shù)據(jù)量小、耗時(shí)短配置控制寄存器很合適AXI-Stream高速數(shù)據(jù)流傳輸沒(méi)有地址像流水線(xiàn)一樣適合搬連續(xù)數(shù)據(jù)AXI-Full / AXI-MM帶地址的內(nèi)存訪問(wèn)適合需要隨機(jī)訪問(wèn)DDR或外設(shè)的場(chǎng)景我們的數(shù)據(jù)是從PL端一個(gè)FIFO里持續(xù)冒出來(lái)的完全不需要隨機(jī)尋址所以畫(huà)Block Design的時(shí)候思路非常清晰AXI DMA一端的S2MMStream to Memory-Mapped通道連到PL的數(shù)據(jù)源MM2SMemory-Mapped to Stream通道可以不用管另一端的S_AXI接口直接接到PS的GP主接口上DMA的控制寄存器通過(guò)AXI-Lite總線(xiàn)訪問(wèn)。2.2 Block Design的關(guān)鍵連接配置搭建Block Design時(shí)幾個(gè)容易踩坑的點(diǎn)需要特別注意ZYNQ7 Processing System里要開(kāi)啟S_AXI_HP0接口這是高速訪問(wèn)DDR的專(zhuān)用口DMA的數(shù)據(jù)得從這里進(jìn)DDR別接到?jīng)]帶緩存一致性的普通GP口上否則性能會(huì)差一個(gè)數(shù)量級(jí)。AXI DMA IP核的Enable Micro DMA選項(xiàng)不要勾選一旦啟用了Micro DMA地址空間被壓縮成小段遇到大數(shù)據(jù)量傳輸根本不夠用。PL端數(shù)據(jù)源和DMA之間加一個(gè)異步FIFO。PL的邏輯時(shí)鐘域和DMA的時(shí)鐘域頻率不同跨時(shí)鐘域容易出問(wèn)題FIFO是最穩(wěn)妥的緩沖方案。DMA中斷必須連到PS的PL-PS中斷端口上這樣DMA搬完一批數(shù)據(jù)后PS才能通過(guò)中斷及時(shí)知道“數(shù)據(jù)來(lái)了”不用輪詢(xún)狀態(tài)寄存器。2.3 一個(gè)被忽略的地址對(duì)齊問(wèn)題我在第一次實(shí)測(cè)時(shí)DMA傳輸了5000字節(jié)的數(shù)據(jù)上位機(jī)收到的前面幾十個(gè)字節(jié)全部是亂碼排查了很久才發(fā)現(xiàn)是地址沒(méi)有做對(duì)齊。AXI DMA的Buffer地址要求是4字節(jié)對(duì)齊更高性能的模式甚至要求32字節(jié)對(duì)齊。如果上位機(jī)收到的數(shù)據(jù)內(nèi)容里有“錯(cuò)位”的情況第一反應(yīng)就是去檢查PS給DMA設(shè)定的起始地址是不是對(duì)齊的。另外如果DMA傳輸長(zhǎng)度不是8字節(jié)對(duì)齊最后一拍會(huì)有些微妙的行為建議把每次傳輸?shù)臄?shù)據(jù)塊大小統(tǒng)一定為128字節(jié)的整數(shù)倍通過(guò)幀填充來(lái)對(duì)齊而不是嚴(yán)格按實(shí)際數(shù)據(jù)長(zhǎng)度穩(wěn)定性會(huì)大幅提升。3. 數(shù)據(jù)傳輸協(xié)議與數(shù)據(jù)幀設(shè)計(jì)網(wǎng)口發(fā)送之前還要把數(shù)據(jù)流“格式化”不然上位機(jī)拿到了一堆數(shù)字也分不清誰(shuí)是誰(shuí)。3.1 為什么需要自定義幀協(xié)議TCP是字節(jié)流協(xié)議它不知道什么是“幀”如果你一次send 1000字節(jié)接收方可能在收滿(mǎn)1000字節(jié)前就收到了前面的800字節(jié)甚至可能一次收到3000字節(jié)你發(fā)三次它合并了一次。為了讓上位機(jī)能從連續(xù)的字節(jié)流里準(zhǔn)確切分出每一包數(shù)據(jù)就必須在PL側(cè)或PS側(cè)給數(shù)據(jù)加一個(gè)“信封”——幀頭、長(zhǎng)度、幀尾、校驗(yàn)。3.2 我使用的幀格式定義我在實(shí)際項(xiàng)目中使用的幀格式非常簡(jiǎn)單但極其可靠幀頭4字節(jié) | 幀長(zhǎng)2字節(jié) | 數(shù)據(jù)序號(hào)4字節(jié) | 有效數(shù)據(jù)N字節(jié) | CRC162字節(jié) | 幀尾2字節(jié)幀頭固定為 0xAA 0x55 0xA5 0x5A用來(lái)在上位機(jī)里做同步頭識(shí)別。幀長(zhǎng)表示有效數(shù)據(jù)長(zhǎng)度最大幀設(shè)計(jì)為不超過(guò)lwIP的單包承載能力。數(shù)據(jù)序號(hào)遞增計(jì)數(shù)器上位機(jī)可以用它檢測(cè)是否有丟幀或亂序?qū)崪y(cè)過(guò)程中靠這4字節(jié)定位了很多問(wèn)題。CRC16對(duì)整幀數(shù)據(jù)算出的校驗(yàn)值能發(fā)現(xiàn)傳輸過(guò)程中是否被篡改或收錯(cuò)。幀尾固定0x0D 0x0A作為接收狀態(tài)的復(fù)位信號(hào)。這個(gè)幀格式的設(shè)計(jì)核心思想就是讓上位機(jī)端程序能用狀態(tài)機(jī)方式解析——讀到幀頭進(jìn)入累積狀態(tài)讀完到幀長(zhǎng)設(shè)定舊長(zhǎng)度CRC校驗(yàn)通過(guò)之后再認(rèn)為是一個(gè)合法包。整個(gè)解析過(guò)程不依賴(lài)任何系統(tǒng)API純手寫(xiě)邏輯兼容性極強(qiáng)上位機(jī)用什么語(yǔ)言寫(xiě)都能按這個(gè)協(xié)議解析。3.3 MTU與分包策略以太網(wǎng)的MTU通常是1500字節(jié)刨掉IP和TCP頭單次TCP能扛的實(shí)際數(shù)據(jù)區(qū)大約1460字節(jié)。如果一次send超過(guò)1472字節(jié)的數(shù)據(jù)協(xié)議棧會(huì)自動(dòng)拆包。雖然lwIP本身支持IP分片但分片重組會(huì)引入額外的處理開(kāi)銷(xiāo)。所以我在設(shè)計(jì)幀格式時(shí)讓“幀長(zhǎng)”字段不設(shè)死上限但在PS端有一個(gè)組包發(fā)送策略每次從DMA緩沖區(qū)取到數(shù)據(jù)后如果長(zhǎng)度超過(guò)1400字節(jié)就自動(dòng)拆成多幀分別加幀頭發(fā)送如果不足1400就攢夠再發(fā)避免把TCP報(bào)文打碎。這種策略讓傳輸效率高了不少上位機(jī)解析也輕松——反正有幀頭幀長(zhǎng)分包后上位機(jī)按規(guī)則拼接就行。3.4 lwIP的TCP發(fā)送buffer配置如果你照我這樣用lwIP千萬(wàn)記得查配置里TCP_SND_BUF和TCP_WND的值。我踩過(guò)一個(gè)大坑——傳輸速度一直上不去用Wireshark抓包發(fā)現(xiàn)TCP窗口被壓得很小后來(lái)查配置發(fā)現(xiàn)默認(rèn)發(fā)送緩沖只有幾K緩沖區(qū)太小會(huì)導(dǎo)致發(fā)送窗口變小吞吐量驟降。把TCP_SND_BUF調(diào)整到32K以上后速度翻了幾倍。4. 上位機(jī)接收與解析實(shí)現(xiàn)要點(diǎn)上位機(jī)方案我用的C#寫(xiě)Winform很多人覺(jué)得上位機(jī)不重要恰恰相反上位機(jī)寫(xiě)不好排查問(wèn)題會(huì)特別痛苦。4.1 上位機(jī)需要實(shí)現(xiàn)的基礎(chǔ)功能一個(gè)可靠的數(shù)據(jù)接收上位機(jī)不需要花哨界面但必須有這些基本功異步TCP接收用Socket.BeginReceive或NetworkStream.BeginRead做異步接收不能在UI線(xiàn)程里直接阻塞讀數(shù)據(jù)否則界面會(huì)卡死。緩沖區(qū)隊(duì)列把收到的原始字節(jié)全部丟進(jìn)一個(gè)線(xiàn)程安全的隊(duì)列數(shù)據(jù)處理線(xiàn)程再?gòu)倪@里取數(shù)據(jù)、按協(xié)議解析。實(shí)時(shí)狀態(tài)顯示實(shí)時(shí)顯示接收速率字節(jié)/秒、接收到的包總數(shù)、CRC錯(cuò)誤計(jì)數(shù)、丟幀計(jì)數(shù)。波形或數(shù)值顯示根據(jù)自己的需求把有效數(shù)據(jù)畫(huà)成曲線(xiàn)或表格方便觀察PL端的數(shù)據(jù)變化。4.2 粘包與半包處理的狀態(tài)機(jī)上位機(jī)最常見(jiàn)的解析問(wèn)題就是粘包和半包。我用的狀態(tài)機(jī)邏輯是收到新數(shù)據(jù) → 塞入接收緩沖區(qū) → 進(jìn)入“找?guī)^”狀態(tài)。找?guī)^從當(dāng)前緩沖區(qū)里掃描是否出現(xiàn) 0xAA 0x55 0xA5 0x5A。找到了幀頭 → 讀取幀頭后4字節(jié)的“幀長(zhǎng)”如果緩沖區(qū)的數(shù)據(jù)長(zhǎng)度不足“幀長(zhǎng)幀尾長(zhǎng)度”等待下一波數(shù)據(jù)補(bǔ)充。長(zhǎng)度夠了 → 取出整幀 → CRC校驗(yàn) → 送入處理函數(shù) → 回到“找?guī)^”。這個(gè)狀態(tài)機(jī)寫(xiě)在ProcessBuffer()方法里每來(lái)一次接收事件就調(diào)用一次完全能扛住千兆網(wǎng)的節(jié)奏。實(shí)測(cè)下來(lái)在100Mbps鏈路、1400字節(jié)幀長(zhǎng)的情況下CPU占用率不足1%。5. 常見(jiàn)問(wèn)題定位與排查技巧每次做這種跨PL、PS、網(wǎng)絡(luò)、上位機(jī)的項(xiàng)目出問(wèn)題大概率不是單一原因而是一連串小問(wèn)題疊加。我把自己踩過(guò)的坑整理成速查表希望能幫你少走彎路。5.1 常見(jiàn)問(wèn)題排查速查表現(xiàn)象可能原因排查手段與解決辦法上位機(jī)收不到數(shù)據(jù)網(wǎng)線(xiàn)不通/端口未開(kāi)先用PC之間點(diǎn)對(duì)點(diǎn)ping測(cè)試鏈路再用網(wǎng)絡(luò)調(diào)試助手直接監(jiān)聽(tīng)端口上位機(jī)收到亂碼地址未對(duì)齊/DMA配置錯(cuò)誤檢查給DMA設(shè)置的Buffer地址是否4字節(jié)對(duì)齊傳輸長(zhǎng)度是否為8的倍數(shù)信號(hào)質(zhì)量差/數(shù)據(jù)偶爾錯(cuò)誤跨時(shí)鐘域未正確隔離PL側(cè)加異步FIFO確保讀時(shí)鐘與寫(xiě)時(shí)鐘互不干擾傳輸速極慢TCP窗口小/發(fā)送緩沖不足調(diào)大lwIP的TCP_SND_BUF和TCP_WND減少分包次數(shù)連上后一段時(shí)間斷連對(duì)端未及時(shí)ACK/TCP連接被重置開(kāi)啟TCP的keepalive或在實(shí)際傳輸時(shí)加定時(shí)心跳包幀錯(cuò)位、解析不出來(lái)幀格式不統(tǒng)一或CRC算法不一致上下位機(jī)使用相同CRC初始值、多項(xiàng)式幀頭幀尾固定值不要隨意動(dòng)上位機(jī)界面卡死同步接收/UI線(xiàn)程阻塞接收邏輯全部用異步方式數(shù)據(jù)處理放到線(xiàn)程池或者后臺(tái)線(xiàn)程5.2 網(wǎng)絡(luò)調(diào)試的三個(gè)階段調(diào)試網(wǎng)絡(luò)部分我習(xí)慣按三個(gè)階段推進(jìn)階段一數(shù)據(jù)源環(huán)回測(cè)試。先把PL端的數(shù)據(jù)源改成一個(gè)固定遞增計(jì)數(shù)器直接在PS里讀取DMA搬上來(lái)的數(shù)據(jù)用串口或者JTAG打印出來(lái)核對(duì)數(shù)值是否連續(xù)遞增。這一步確保PL→PS通路無(wú)問(wèn)題。階段二TCP回環(huán)測(cè)試。在PS端寫(xiě)一個(gè)簡(jiǎn)單echo服務(wù)上位機(jī)連上后發(fā)什么回什么確認(rèn)TCP連接和應(yīng)用層收發(fā)沒(méi)有問(wèn)題。階段三全鏈路測(cè)試。把PL數(shù)據(jù)源打開(kāi)數(shù)據(jù)經(jīng)DMA→PS→lwIP→端口發(fā)送上位機(jī)完整收幀。通過(guò)數(shù)據(jù)序號(hào)字段檢查丟幀率。這個(gè)方法幫我節(jié)省了大量排查時(shí)間——如果階段一就通了問(wèn)題就在網(wǎng)絡(luò)如果階段三才出問(wèn)題問(wèn)題就可能在上位機(jī)或者帶寬規(guī)劃上。5.3 數(shù)據(jù)帶寬估算與性能驗(yàn)證不要等到板子跑起來(lái)才發(fā)現(xiàn)帶寬不夠用我在寫(xiě)代碼之前先按公式粗算了一次假如PL側(cè)采樣率是10MSPS每個(gè)采樣點(diǎn)16bit則原始數(shù)據(jù)率為 10M × 2 20MB/s。以太網(wǎng)TCP有效速率在百兆環(huán)境下理論最多約 11MB/s這明顯不夠。所以我果斷把網(wǎng)口鏈路改到千兆實(shí)測(cè)TCP吞吐約85MB/s以上20MB/s的負(fù)荷只占用了不到四分之一完全滿(mǎn)足需求。如果你的數(shù)據(jù)率也接近百兆網(wǎng)極限那就得考慮壓縮、降采樣或者換千兆網(wǎng)卡/UDP總之先算清楚再動(dòng)手別把代碼寫(xiě)完了才發(fā)現(xiàn)物理帶寬不夠。6. 后續(xù)可以怎么擴(kuò)展這個(gè)基礎(chǔ)鏈路跑通之后能擴(kuò)展的方向很多我按性?xún)r(jià)比排序PL端添加濾波或FFT在數(shù)據(jù)進(jìn)FIFO之前加一個(gè)浮點(diǎn)濾波核輸出的是濾波后的數(shù)據(jù)流上位機(jī)顯示更干凈。多通道或多DMA搬移用多個(gè)AXI DMA通道分別管理不同來(lái)源的數(shù)據(jù)上位機(jī)用幀頭里的數(shù)據(jù)類(lèi)型字段區(qū)分。PS端加Flash/SD卡存儲(chǔ)把原始數(shù)據(jù)和解析結(jié)果存起來(lái)以便后續(xù)離線(xiàn)分析配合FTP服務(wù)還能遠(yuǎn)程取文件。切換Linux系統(tǒng)如果未來(lái)要跑復(fù)雜算法或做網(wǎng)頁(yè)服務(wù)再把PS端系統(tǒng)遷移到Linux底層鏈路邏輯可以復(fù)用大部分代碼。最后分享一個(gè)我實(shí)際操作中的體會(huì)這類(lèi)跨端通信項(xiàng)目最怕的不是技術(shù)難而是結(jié)果“看起來(lái)正常但數(shù)據(jù)是錯(cuò)的”。所以從一開(kāi)始就要把CRC校驗(yàn)、幀計(jì)數(shù)這些可靠性機(jī)制做進(jìn)去別圖省事刪掉。用一個(gè)遞增計(jì)數(shù)器做數(shù)據(jù)源驗(yàn)證既是調(diào)試手段也是數(shù)據(jù)正確性的底線(xiàn)保障。做FPGA這種事多花10分鐘加保險(xiǎn)能幫你省下10小時(shí)的排查時(shí)間這筆賬怎么算都劃算。本文還有配套的精品資源點(diǎn)擊獲取