ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Zynq+AD9361的开源802.11基带FPGA设计与实现

基于Zynq+AD9361的开源802.11基带FPGA设计与实现

简介:openwifi是基于软件无线电的开源IEEE 802.11/Wi-Fi基带芯片FPGA设计项目,面向Zynq平台与无线通信开发者,目标是提供兼容Linux mac80211协议栈的完整收发链路,涵盖从驱动、软件到FPGA硬件的全栈参考实现。压缩包共140个文件、约8.7MB,包含28个shell脚本、17个C源文件、14个头文件、12个Markdown说明、makefile构建脚本、设备树dts/dtb、ELF固件、配置文件与license等,按功能可对应FPGA工程、Linux驱动、测试脚本和文档模块,目录组织便于按需检索。目前已有3425人学习,适合具备Verilog/Vivado或SDR基础的中高级工程师深入研究。内容涉及AD9361射频控制、Xilinx DMA传输、收发接口与中控逻辑等关键模块,并实现802.11a/g/n、20MHz带宽、70MHz至6GHz射频范围、DCF(CSMA/CA)低MAC层(10us SIFS)、CSI信道状态信息、IQ捕获、RTS/CTS阈值配置等特性,可直接对照源码理解OFDM星座映射、信道均衡与MAC竞争机制,也可作为在此基础上扩展自定义基带算法的FPGA+SDR设计起点。

1. 从黑盒到开源:为什么需要一套802.11基带芯片的FPGA设计方案

做Wi-Fi相关的SDR开发,最难受的不是找不到射频芯片,而是拿到手里的802.11基带全是黑匣子。商用Wi-Fi芯片把PHY、MAC时序、射频校准全部封装进硅片,想看OFDM符号怎么映射、想在CSMA/CA退避窗口动一刀、想抓一段未经处理的IQ数据,全都做不到。于是就有了开源IEEE802.11 Wi-Fi基带芯片FPGA设计的需求:用Zynq SoC加AD9361射频前端,在可编程逻辑里重新实现802.11基带处理,并让硬件原生挂进Linux的mac80211协议栈,成为一张能联通的SDR网卡。这套方案适合三类人:做软件无线电的嵌入式工程师,拿FPGA做通信课设或毕设的学生,以及需要PHY级可观测性的协议研究者。下面按总体架构、基带实现、软件栈、避坑经验、验收答辩五步展开这套完整设计。

2. 总体架构设计:Zynq+AD9361硬件平台与三平面职责划分

这套方案的总架构用一句话概括:Zynq的FPGA部分承载802.11的物理层基带和时序敏感的MAC原语,ARM部分承载Linux驱动与mac80211协议栈,AD9361负责射频收发。三个芯片各司其职,剩下的事情就是定义它们之间的接口。接口定义得清晰,后面每一层都能独立开发和调试;接口模糊,任何一处改动都会牵扯两侧联动返工。下面按硬件选型、接口平面、DMA数据通路、工程目录四个角度把总体设计展开。

2.1 硬件选型:为什么是Zynq而不是纯FPGA或纯ARM

802.11基带对硬件平台的要求其实是矛盾的。OFDM收发必须以符号粒度实时处理,20MHz带宽下一个OFDM符号周期只有4微秒,这决定了基带不能跑在通用处理器上;但完整的802.11又要跑协议栈,要支持wpa_supplicant、hostapd、iperf这些成熟的Linux生态工具,这又需要一个能跑操作系统的处理器。纯FPGA平台想同时满足两者,只能在FPGA里塞软核,而软核跑Linux的维护代价极高,吞吐量也很难撑住mac80211的收发路径。纯ARM SoC平台反过来,基带的实时计算在ARM上按期完成不现实。

Xilinx Zynq恰好解决了这个矛盾:双核ARM Cortex-A9和FPGA fabric集成在同一颗芯片里,通过AXI总线互联。FPGA侧做基带和时序敏感的MAC原语,ARM侧跑Linux和mac80211。这个选择不是拍脑袋,而是由802.11协议本身特征决定的——物理层的载波调制、信道编解码是纯计算任务,适合并行硬件;MAC层大部分功能是事件驱动、查表和状态管理,适合软件实现。两者的分界线正好落在Zynq的软硬分界上。

射频前端选AD9361,一颗来自Analog Devices的集成宽带收发器,覆盖70MHz到6GHz,支持2发2收,内部集成混频、滤波、自动增益控制和模数转换,对外提供数字IQ接口与SPI控制接口。FPGA只需要按约定速率接收IQ采样,就能完成从天线到基带的全部射频工作。这套组合是软件无线电里最成熟的搭配,资料多、坑也基本被踩平了,适合作为课程设计和毕设的基础平台。

选型时容易被忽略的是时钟方案。AD9361的参考时钟、FPGA的采样时钟、MAC的符号时钟必须同源或严格对齐,否则基带的载波频偏估计会始终带着一个固定偏差。通常的做法是让AD9361输出采样时钟给FPGA,FPGA内部所有基带处理模块都使用这个时钟域,避免异步时钟转换为系统带进不确定性。

2.2 三平面接口划分:IQ数据流、控制寄存器与中断事件

ARM和FPGA之间的通信需求其实有三种完全不同的特征,I一般会在一开始就按三个平面划分接口,而不是笼统地画一根AXI总线。数据平面负责IQ采样和802.11帧搬运,吞吐量大、不能逐包经过CPU,全部走DMA;控制平面负责信道频率、发射功率、速率等PHY参数配置,变化频率低但对实时性有要求,用AXI-Lite寄存器最合适;事件平面负责收发完成的中断通知,要求低时延但必须做聚合去抖,否则高速流量下中断开销会吃掉吞吐量。

平面承载内容接口类型关键设计点
数据平面IQ采样与802.11帧AXI DMA描述符链深度、缓存一致性
控制平面PHY参数配置与状态回读AXI-Lite寄存器状态位与配置位分离
事件平面收发完成、DMA完成中断线中断聚合、去抖

三个平面在驱动里也对应不同的处理路径:数据平面走DMA中断回调,控制平面走ioctl或cfg80211回调,事件平面走tasklet或workqueue。接口从硬件到软件保持一致的分层,调试时才能按层面定位问题。我见过不少翻车案例,都是因为把控制逻辑混在数据通路里,导致一个寄存器写操作的时序问题影响整条收发链路。

2.3 DMA数据通路与吞吐量预算:先算账再动手

设计数据通路之前最好先做一次吞吐量预算。20MHz信道带宽下,AD9361以20MHz采样率输出,I/Q各16位,FPGA和AD9361之间的原始IQ速率是20M乘以2乘以16位,即640Mbps。这个速率在FPGA内部走并行总线没有问题,关键在FPGA和ARM之间的那段:这里传输的不是IQ而是解调后的MAC帧。802.11a/g最高速率54Mbps,对应64QAM、3/4码率,加上帧头和管理帧开销,实际DMA带宽需求约60Mbps,AXI DMA在Zynq上轻松满足。

真正容易出问题的是描述符链深度和缓存一致性。TX方向如果驱动下发描述符的速度赶不上FPGA读取,发射链路会空转拉高时延;RX方向如果描述符耗尽,FPGA收完一帧后找不到可用缓冲区只能丢帧。通常每方向至少准备8到16个描述符,并在驱动里用双缓冲乒乓切换。DMA缓冲区必须用dma_alloc_coherent分配,而不能用普通kmalloc,否则ARM写完数据后FPGA读到的可能是cache里的旧数据。

提示:先查DMA描述符深度和缓存一致性,再怀疑基带算法,这是排查吞吐量问题的固定顺序。

2.4 FPGA工程与Linux驱动的目录组织

同时包含FPGA代码、HLS工程、Linux驱动和测试脚本的项目,目录结构如果不设计好,后期维护会很痛苦。我一般按下面方式组织,FPGA工程和Linux驱动分开编译,两者用寄存器地址和中断号约定接口。

openwifi/ # 方案根目录 fpga/ rtl/ # 手写Verilog基带代码 hls/ # Vivado HLS源码与工程 blocks/ # block design与IP配置 constraints/ # 管脚约束与时钟约束 linux/ driver/ # 内核驱动模块源码 tools/ # 用户态配置与调试工具 docs/ # 架构文档与验收清单

接口定义要写在一个共享头文件里,FPGA侧的寄存器地址映射和驱动侧的结构体字段共用同一份定义,避免两边各改各的对不上。驱动不要直接操作裸寄存器,封装成openwifi_set_channel、openwifi_get_stats这类函数,后面更换硬件平台时只改底层实现,上层mac80211对接代码可以原样保留。

3. 基带实现:OFDM收发链路与802.11 MAC的FPGA落地

这一章是整套方案的核心工作量所在。基带实现要回答三个问题:发射链路怎么把MAC帧变成IQ采样,接收链路怎么从IQ采样还原出MAC帧,以及CSMA/CA时序放在哪里实现才能满足802.11的微秒级要求。三者都落在FPGA里,但实现方式并不相同,有的是手写Verilog,有的用Vivado HLS做C综合,下面逐节拆开。

3.1 发射链路流水线:从MAC帧到OFDM符号

802.11a/g的发射链路是一组固定的信号处理步骤。MAC层下发帧后,先做CRC和加扰,经过卷积编码、打孔、交织,映射成QAM符号,插入导频后做64点IFFT,加上循环前缀和窗函数,最后按I/Q两路送给AD9361。在FPGA里,这条链路按流水线组织,每一级是独立模块,模块之间用AXI-Stream握手信号连接。

流水级模块处理内容
1加扰器帧数据加扰,能量扩散
2卷积编码与打孔1/2码率卷积编码,按速率打孔
3交织器频率交织与时间交织
4星座映射BPSK / QPSK / 16QAM / 64QAM
5IFFT插入导频后做64点IFFT
6循环前缀与加窗拼接CP,频谱成型
7IQ输出16位I/Q并行送AD9361

握手信号是整个发射链路不出乱子的关键。tvalid表示本拍数据有效,tready表示下游可接收,tlast表示一帧结束,只有tvalid和tready同时为高这一拍才真正传输。加扰器和编码器之间、交织器和映射器之间都要用这套协议做反压。如果不做反压,某个模块处理慢一拍,后面数据全部错位,表现出来的现象是频谱看着正常但解调出的比特全错。

module axis_width_convert_32to8 ( input wire aclk, input wire aresetn, input wire [31:0] s_axis_tdata, input wire s_axis_tvalid, output reg s_axis_tready, input wire s_axis_tlast, output reg [7:0] m_axis_tdata, output reg m_axis_tvalid, input wire m_axis_tready ); reg [31:0] buf_32; reg load_valid; reg [1:0] byte_cnt; assign s_axis_tready = !load_valid && m_axis_tready; always @(posedge aclk) begin if (!aresetn) begin load_valid <= 1'b0; byte_cnt <= 2'd0; m_axis_tvalid <= 1'b0; end else if (s_axis_tvalid && s_axis_tready) begin buf_32 <= s_axis_tdata; load_valid <= 1'b1; byte_cnt <= 2'd0; m_axis_tvalid <= 1'b1; end else if (m_axis_tvalid && m_axis_tready && byte_cnt == 2'd3) begin load_valid <= 1'b0; m_axis_tvalid <= 1'b0; end else if (m_axis_tvalid && m_axis_tready) begin byte_cnt <= byte_cnt + 1'b1; end end always @(*) begin case (byte_cnt) 2'd0: m_axis_tdata = buf_32[31:24]; 2'd1: m_axis_tdata = buf_32[23:16]; 2'd2: m_axis_tdata = buf_32[15:8]; default: m_axis_tdata = buf_32[7:0]; endcase end endmodule

这段代码是发射路径入口的32位转8位模块,同时实现了反压逻辑。s_axis_tready只有在内部缓冲空且下游可接收时才拉高,确保上游DMA不会在FIFO满时继续推数据。load_valid信号表示当前缓冲里还有未发完的字,等4个字节全部送出后才允许接收新数据。实际工程里这个模块前还会加一级深度为2的小FIFO用来吸收握手毛刺,但反压的逻辑结构就是这个思路。

3.2 接收链路:包检测、信道估计与定点均衡

接收链路比发射链路复杂得多。从AD9361进来的IQ数据先做自动增益控制,然后用L-STF短训练序列做包检测和粗频偏估计,用L-LTF长训练序列做精同步和信道估计,再做FFT、信道均衡、解交织、Viterbi译码,最后还原成MAC帧。每一个步骤都有对应硬件模块,任何一个模块的精度不足都可能让后面的数据全盘报废。

包检测是最讲究的一步。通常做法是延迟相关,把接收信号和延迟16个采样点的版本做相关,归一化后与阈值比较。阈值调高,灵敏度下降,远端信号检测不到;阈值调低,噪声误触发,系统一直在处理假包。这个阈值在不同环境要能通过寄存器动态调整,不能写死在RTL里。调参过程带有一定玄学色彩,有同学在实验室调好的阈值拿到户外就频繁误触发,最后在驱动里加了自动阈值校准才稳定。

信道均衡适合用HLS写。它是典型的循环计算密集任务,每个数据子载波都要除以复数信道系数,用C++描述比手写Verilog清晰得多。下面是简化后的信道均衡函数,用定点复数计算避免浮点资源消耗。

#include "hls_stream.h" #include "ap_fixed.h" typedef ap_fixed<16, 8> cmplx_t; void channel_equalize( hls::stream<cmplx_t>& rx_in, hls::stream<cmplx_t>& h_est, hls::stream<cmplx_t>& eq_out, int n_subcarriers ) { #pragma HLS INTERFACE axis port=rx_in #pragma HLS INTERFACE axis port=h_est #pragma HLS INTERFACE axis port=eq_out for (int i = 0; i < n_subcarriers; i++) { #pragma HLS PIPELINE II=1 cmplx_t r = rx_in.read(); cmplx_t h = h_est.read(); cmplx_t e; e.real = (r.real * h.real + r.imag * h.imag); e.imag = (r.imag * h.real - r.real * h.imag); eq_out.write(e); } }

注意这里没有把复数除法完整塞进循环,只做了r乘以h共轭的乘法部分,除以|h|^2被挪到信道估计模块预先算好。原因很简单:除法在FPGA里要消耗大量DSP且拉低II(迭代间隔),而|h|^2对同一帧内所有符号是固定的,提前算一次即可。这个改动是HLS优化里收益最明显的一步。ap_fixed<16,8>表示16位宽、8位整数位,精度和资源之间需要按EVM指标反复权衡。

3.3 CSMA/CA与MAC时序:为什么收发时序必须放FPGA

802.11 MAC协议有一个所有做软件无线电的人都会撞上的问题:时间敏感的原语在Linux软件里做不出来。OFDM模式下SIFS只有16微秒,时隙只有9微秒。AP收到数据帧后必须在SIFS内回复ACK,否则对端认为丢包重传。Linux内核调度延动辄几十微秒,中断处理再快也达不到这个时序。

因此这套方案的MAC实现是硬件和软件分工的:FPGA内实现CSMA/CA状态机,负责CCA信道检测、退避计数、帧间隔计时、ACK与CTS自动生成;Linux驱动和mac80211负责关联管理、信标调度、速率选择、重传策略这些对时序不敏感的部分。硬件CSMA/CA状态机的工作过程大致是:空闲时持续做CCA检测,信道忙则进入退避,退避计数器在每个空闲时隙递减,归零后开始发送;发送结束后按帧类型启动SIFS或DIFS计时。

这个分工直接影响整机吞吐量。退避和帧间隔计时如果放错地方,哪怕只差几个微秒,实际表现就是数据对端能收到但收不到ACK,TCP吞吐量上不去,抓包看全是重传。这也是我建议调试时先开monitor模式抓两端包、确认ACK时序正常后再优化吞吐量的原因。硬件状态机配合逻辑分析仪,能直接看到从RX完成到TX启动的延时,这是软件实现给不了的可见性。

3.4 HLS与Verilog的分工:纯计算用C综合,含状态机手写RTL

工程里最常被问的问题是:既然用了Vivado HLS,是不是所有模块都可以用C++写?答案是否定的。HLS适合数据流密集、循环结构清晰、对时延不敏感的处理,比如FFT、信道均衡、相关器、滤波器;不适合代码里满是状态跳转和精确时序控制的部分。

必须手写Verilog的包括三类。一是MAC状态机,CSMA/CA的退避和帧间隔计时要求纳秒级精度,C综合出来的状态机时序不可控;二是AXI接口与跨时钟域逻辑,属于接口胶水代码,手写更直观且方便做时序约束;三是发射路径从加扰到调制的流水线,虽然也可以HLS,但手写Verilog能精确控制每一拍的流水级数,吞吐率更容易保证。

实用的策略是先画出整个收发链路的数据流图,标出哪些模块是纯计算、哪些含状态机。纯计算的用HLS,含状态机的用Verilog,两类IP在Vivado block design里用AXI-Stream连接,HLS生成的IP自动带AXI接口,衔接成本很低。综合后如果时序不收敛,优先检查跨时钟域路径,而不是急着改代码。

4. 软件栈与驱动:mac80211对接和SDR控制面的Linux落地

硬件基带做得再好,最终都要表现为Linux里一张能用的无线网卡,能用iw命令配置,能被wpa_supplicant连接AP,能被hostapd配置成AP。这一步靠的是Linux驱动把FPGA的能力完整暴露给mac80211协议栈。驱动工作分三层:注册层让系统识别设备,控制层处理信道和速率配置,调试层让开发者看得见PHY内部状态。

4.1 驱动注册流程:从平台设备到ieee80211_alloc_hw

驱动首先是一个平台设备驱动,probe函数里做寄存器映射、申请DMA通道、注册中断,然后调用ieee80211_alloc_hw分配硬件实例,填充ops回调结构体。mac80211通过ops里的回调调驱动,驱动通过读写AXI-Lite寄存器和操作DMA描述符调FPGA。下面是一段驱动初始化骨架,注意这套LinuxC写法的关键点。

#include <linux/module.h> #include <linux/platform_device.h> #include <net/mac80211.h> static const struct ieee80211_ops openwifi_ops = { .tx = openwifi_tx, .start = openwifi_start, .stop = openwifi_stop, .config = openwifi_config, .add_interface = openwifi_add_interface, }; static int openwifi_probe(struct platform_device *pdev) { struct ieee80211_hw *hw; struct openwifi_priv *priv; hw = ieee80211_alloc_hw(sizeof(*priv), &openwifi_ops); if (!hw) return -ENOMEM; priv = hw->priv; priv->regs = devm_platform_ioremap_resource(pdev, 0); priv->tx_chan = dma_request_chan(&pdev->dev, "tx"); if (IS_ERR(priv->tx_chan)) return PTR_ERR(priv->tx_chan); return ieee80211_register_hw(hw); }

ops里的每个回调对应mac80211在某个时机要执行的硬件操作。start在网卡启用时被调用,驱动在这里打开FPGA基带、把AD9361切到收发状态、复位DMA描述符链;stop则相反。tx是核心回调,mac80211通过它下发802.11帧,驱动把帧拷贝到DMA缓冲区、写描述符、再写一个启动寄存器通知FPGA取数。注意tx回调必须快速返回,不能等发送完成才return,否则mac80211的发送路径会被卡死,吞吐量断崖式下跌。

4.2 控制面实现:信道切换、速率选择与发射功率的落地

mac80211的config回调是控制面总入口,信道切换、频宽变化、发射功率调整都会汇聚到这里。在SDR网卡上,一次信道切换要同时操作两级硬件:先通过AXI-Lite把目标信道号写入PHY寄存器,再通过SPI把对应的本振频率写入AD9361。

这里有一个容易被忽略的点:AD9361的校准是异步的,写入频率后需要一段时间完成内部校准。驱动不能立即上报切换完成,通常的做法是查询AD9361的校准状态寄存器,或者加固定延时再返回。如果忽略这一步,信道切换后接收灵敏度会明显下降,而且问题时有时无,很难定位。

速率选择方面,mac80211的minstrel算法会为每个对端维护速率表,发送时通过txinfo里的rate字段告诉驱动本次使用哪个速率。驱动把这个速率翻译成FPGA能理解的调制编码参数写入TX描述符,FPGA在发送该帧时按参数执行。发射功率同理,mac80211下发功率值,驱动换算成AD9361的增益寄存器值经SPI写入。换算关系要查数据手册,不同频段和衰减器组合下曲线并不线性,不能用一个固定公式套到底。

4.3 调试三板斧:monitor抓包、IQ采集与PHY统计寄存器

一套SDR网卡没有调试工具,遇到问题只能靠猜。我建议至少准备三件工具。第一件是Linux侧抓包,把网卡设成monitor模式后用tcpdump抓802.11管理帧和数据帧,配合Wireshark看帧类型、速率、信号强度,这是验证MAC行为最直接的途径。第二件是IQ采集,FPGA里做一个触发捕获模块,收到寄存器触发后把指定长度的原始IQ数据存入DDR,用户态工具导出成文件,再用Python画频域波形和星座图。

import numpy as np import matplotlib.pyplot as plt iq = np.fromfile("rx_iq.bin", dtype=np.int16) iq = iq[: len(iq) // 2 * 2].reshape(-1, 2) i, q = iq[:, 0].astype(float), iq[:, 1].astype(float) comp = (i + 1j * q) / 2048.0 plt.plot(comp.real, comp.imag, '.', markersize=0.5) plt.axis('equal') plt.grid(True) plt.show()

星座图散点聚成几个清晰簇,说明接收链路的同步和均衡正常;散点呈圆环说明频偏没校正干净;中心一团加外圈稀疏点说明AGC增益不足、量化噪声过大。这个脚本是调试接收链路性价比最高的工具。第三件是PHY统计寄存器,FPGA每收一帧就把该帧的RSSI、CFO估计值、信道估计误差累积到统计寄存器,驱动经debugfs导出。用户反馈信号差时先看统计,能快速区分是射频问题、同步问题还是链路质量问题。

5. 常见问题与避坑:时钟域、DMA饥饿和ACK时序的典型翻车点

这一章专门记录这套方案里最容易翻车的问题,每条按现象、原因、解决的顺序写。FPGA通信系统最折磨人的不是问题难,而是现象和原因隔得很远,不按方法排查会浪费大量时间。以下五条来自实际调试记录,基本覆盖了从射频前端到驱动软件的主要坑位。

5.1 AD9361初始化后IQ数据全是固定值

现象:设备上电后读取IQ数据,I路和Q路输出恒定不变,不随时间变化,频谱上看不到任何底噪波形。

原因:AD9361没有完成内部校准,或者参考时钟没有起来。多数情况是初始化流程漏了校准步骤,或者FPGA侧的时钟使能信号没拉对。

解决:先确认FPGA对AD9361的使能信号和时钟输出配置,再在驱动初始化里执行完整校准流程,并等待校准完成标志后再开始采集。校准未完成就开始读取IQ,读到的就是无效数据。

5.2 AP模式下能收帧但发不出ACK

现象:设备作为AP启动后,能收到站点发来的关联请求,但站点始终收不到ACK,抓包只有上行没有下行,关联始终不成功。

原因:ACK发送时机由FPGA的MAC状态机控制。如果SIFS定时不准,或CSMA/CA状态机收到帧后没有及时切换到发送ACK状态,就错过16微秒响应窗口。

解决:在FPGA里检查SIFS定时器的时钟频率配置是否为20MHz整数倍关系,并用逻辑分析仪抓RX完成到TX启动的延时。正常情况下这个延时必须小于SIFS,超过就说明状态机跳转路径上有额外等待周期。

5.3 长时间流量后DMA停止,dmesg报描述符超时

现象:高负载运行半小时后吞吐量突然掉到零,dmesg出现DMA超时错误,恢复需要重新加载驱动。

原因:RX方向描述符被消耗完,FPGA收到新帧时没有可用缓冲区只能丢弃,而驱动因为中断丢失没有及时补充描述符。描述符链太浅是直接原因。

解决:把描述符数量增加到16个以上,在中断处理里无条件补充描述符,并在FPGA侧加一个因描述符耗尽而丢帧的计数器。出现吞吐量掉零时先看这个计数器,而不是急着怀疑基带算法。

5.4 FFT输出与Matlab仿真结果对不上

现象:接收链路中间模块输出与Matlab仿真相比,星座点整体旋转且幅度明显不对,看起来像算法实现错误。

原因:HLS生成的FFT IP默认在每一级做缩放,缩放因子与Matlab归一化方式不一致,导致符号幅度指数级偏差。

解决:在HLS工程里显式指定FFT的缩放策略,并让它与仿真脚本里的归一化保持一致。改完后用同一段测试向量分别过Matlab和FPGA,逐级对比中间结果,定位是第一级还是后级引入的偏差。

5.5 信道切换后接收灵敏度明显下降

现象:从一个信道切到另一个信道后,信号强度显示正常,但接收灵敏度变差,远距离站点连不上或频繁掉线。

原因:切换信道时只写了本振频率,没有重新执行AD9361的增益校准和DC偏移校准。不同频率下前端增益特性不同,不校准就会错。

解决:在驱动的信道切换流程里,SPI频率配置后强制执行完整校准序列,等校准完成再上报信道切换完成。校准未完成就上报,mac80211会提前开始收发,这个问题在实验环境里很容易被忽略。

6. 验收与答辩:用四个层级的测试矩阵证明基带设计成立

6.1 验收测试矩阵

方案做完不能只停在能ping通,要有一套能说服导师或评审的验收矩阵。我习惯把验收分成四个层级,每层都有明确测试项、工具和通过标准。

层级测试项工具与方法通过标准
基础通信AP与STA关联、双向pinghostapd + wpa_supplicant关联成功,ping无丢包
性能指标TCP/UDP吞吐量、重传率iperf3 + tcpdump统计吞吐量接近速率档位理论值,重传率低于5%
协议行为信标、关联、ACK时序monitor模式抓包分析帧间隔与802.11规范一致
SDR扩展星座图、EVM、IQ采集自写Python分析脚本星座图汇聚清晰,EVM在可接受范围

基础通信层解决"能不能用"的问题,性能层解决"好不好用"的问题,协议层解决"合不合规"的问题,SDR扩展层验证这套平台相对商用芯片的独特价值。四个层级按顺序执行,前一层不过就不做后一层。协议层最容易卡住,因为管理帧的时序问题在ping通的情况下依然可能存在,必须抓包确认。

6.2 答辩怎么讲:从架构决策而非代码细节切入

答辩时最容易犯的错是顺着实现顺序讲代码,评审听得一头雾水。更好的讲法是按需求、设计决策、验证结果三段走。先讲商用Wi-Fi黑匣子的痛点,引出为什么需要一套开源可编程基带;再讲哪些功能放FPGA、哪些放Linux、为什么这样切,把三平面接口和CSMA/CA下沉到硬件的决策讲清楚;最后用验收矩阵里的数据证明设计成立。中间穿插一两个调试故事,远比罗列模块清单有说服力。

讲解时有一个关键话术:评审问"怎么实现"时,先用"为什么这么设计"回答,再落到具体实现。比如问ACK怎么发出去,先答SIFS的16微秒预算决定了它必须放在FPGA状态机,再讲状态机跳转和SIFS定时器配置。这样回答既证明了设计思考,又展示了对细节的掌控。

从这次之后,我每次交付FPGA通信类方案都强制自己先跑一遍验收矩阵,再写讲稿。因为验收矩阵会逼你直面那些看起来能工作但经不起追问的角落,讲稿则逼你把每个设计决策的理由说圆。这套从需求到验收的路径,希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进