简介这是openwifi开源项目一套基于SDR软件定义无线电的完整IEEE802.11/Wi-Fi基带芯片FPGA设计兼容Linux mac80211协议栈面向FPGA开发者、无线通信研究人员以及希望深入理解Wi-Fi物理层与MAC层实现的学习者可用于学习、研究及二次开发。项目基于Xilinx FPGA与AD9361射频前端支持802.11a/g/n、20MHz带宽与70MHz至6GHz频率范围实现了FPGA内DCFCSMA/CA低MAC层含10us SIFS时序、CSI信道状态信息采集、IQ捕获及可配置信道访问参数等特性。压缩包共140个文件以c/h源码、sh脚本、makefile构建配置、md说明文档为主辅以dts/dtb设备树、elf固件、jpg/png图片等其中源码覆盖驱动与FPGA逻辑脚本与配置文件便于环境构建整体仅8.7MB目录层次清晰便于按驱动、软件与FPGA设计分块检索。目前已有3425人学习与下载。通过打包内容可系统获取开源SDR Wi-Fi的软硬件分层实现、OFDM基带处理与DMA传输细节、ad9361配置方法以及mac80211兼容驱动的整合思路是研究SDR与FPGA无线通信的实用参考资料。1. openwifi一份能当 Wi-Fi 网卡用的 IEEE 802.11 基带 FPGA 设计做软件无线电的人大概都有过这种经历OFDM 解调链路好不容易自己跑通了能解出星座点可一旦要跟真实手机连接、要处理 MAC 层重传和竞争窗口时间确定性就崩了整个工程卡在“物理层能用、协议栈不能连”的尴尬夹缝里。openwifi 解决的就是这个问题——它是一份基于 Xilinx Zynq FPGA 的开源 IEEE 802.11 Wi-Fi 基带设计配合 AD9361 射频前端和 Linux 驱动可以把一块带 FPGA 的板子变成一张标准 wlan0 网卡支持 AP、STA、monitor 模式能跑 hostapd也能用 Wireshark 抓 802.11 帧。适合想深入研究 Wi-Fi PHY/MAC 实现、或者需要定制无线协议栈的开发者。2. 架构全景Zynq、AD9361 与 Linux 三方怎么组成一张网卡openwifi 最吸引人的地方是它把一张 Wi-Fi 网卡从芯片级做了开源的软硬件切分。它不是单纯“用 FPGA 写个收发信机”也不是“Linux 里加一个虚拟网卡驱动”而是把 IEEE 802.11 协议栈拆成三层Linux 跑在 Zynq 的 ARM 核上负责上层协议、配置和用户态工具FPGA 完成 OFDM 基带、CSMA/CA 时序、加扰、FCS 校验这些对实时性极其敏感的部分AD9361 则负责射频信号的收发。三者通过 AXI 总线和配置寄存器咬合在一起。2.1 Zynq PS 与 PL 的软硬分工为什么 MAC 层要拆两半Zynq 平台的特点就是 PSProcessing System和 PLProgrammable Logic共用一个芯片。看 openwifi 的代码结构你会发现它的边界划得很清楚。PS 侧的 Linux 驱动通过platform_driver注册设备树里指定了 openwifi 的寄存器基地址、中断号和 DMA 通道而 PL 侧的 FPGA 逻辑里实现了完整的 802.11 PHY 和大部分 MAC 功能。这种划分不是拍脑袋而是无线协议栈的现实约束CSMA/CA 的 backoff 计时精度要到微秒级Linux 调度器根本给不了确定性所以这些逻辑必须落到 FPGA 硬件状态机里。功能实现位置承担内容配置管理Linux 驱动 用户态工具寄存器读写、PHY 参数配置、mac80211 回调TX/RX 数据搬运AXI DMA FPGA 缓存skb 与 FPGA 内存之间的批量拷贝OFDM 调制解调FPGAFFT/IFFT、均衡、解映射、ViterbiCSMA/CA 时序FPGA 状态机backoff 计数、NAV 维护、帧间隙射频控制AD9361变频、AGC、IQ 采样我读这套代码时的直观感受是驱动层只需要关心两个“接口”。一个是内存映射的寄存器组用来下发发射参数、读取状态、触发软复位另一个是 DMA 描述符环用来把 Linux 的sk_buff转换成 FPGA 能处理的帧描述符。应用层看到的是一个标准网络设备完全感知不到底层是 FPGA。2.2 AD9361 射频前端与数据通路从基带 IQ 到天线的最后一公里AD9361 是一颗 2 发 2 收的宽带收发器输出 12 位 IQ 数据射频范围覆盖 70 MHz 到 6 GHz。openwifi 选择它除了开源硬件社区资料多以外还因为它有灵活的接口模式FPGA 和 AD9361 之间可以通过 CMOS 并行口或 LVDS 差分口连接。常见做法是用 LVDS因为数据速率高而且能减少 IO 翻转噪声对射频的干扰。时钟关系也很关键AD9361 输出的采样时钟要作为 FPGA 侧基带时钟的参考。如果配置不当FPGA 解调出来的星座点会旋转这是调 SDR 最容易翻车的地方。// 示意FPGA 内 AD9361 RX 接口采样逻辑 wire ad9361_rx_clk; wire [11:0] ad9361_rx_i; wire [11:0] ad9361_rx_q; wire ad9361_rx_valid; always (posedge ad9361_rx_clk) begin if (ad9361_rx_valid) begin sample_i ad9361_rx_i; sample_q ad9361_rx_q; end end这段代码是 FPGA 接收链路的入口采样。ad9361_rx_clk是 AD9361 送来的采样时钟换到 LVDS 模式时通常是双沿采样需要注意时序约束ad9361_rx_valid由 AD9361 的帧同步信号引出标志当前样本有效。整个过程没有任何 Linux 参与基带数据从射频芯片一路流到 FPGA 内部的同步和 AGC 模块。拿到这套工程后先把这级接口约束跳通后面解调出来的数据才可信。2.3 mac80211 之上的 Linux 驱动让 FPGA 变成 wlan0驱动层是 openwifi 和普通 FPGA SDR 项目最大的分水岭。大多数 SDR 工程只能把采样数据交给用户态程序处理而 openwifi 在 Linux 内核里实现了ieee80211_ops接口。这意味着 mac80211 会把它当成一块真正的无线网卡所有协议栈上层逻辑比如身份认证、密钥管理、功率管理都可以直接用 Linux 原有的软件体系。static const struct ieee80211_ops openwifi_ops { .start openwifi_start, .stop openwifi_stop, .tx openwifi_tx, .config openwifi_config, .add_interface openwifi_add_interface, .remove_interface openwifi_remove_interface, .sta_add openwifi_sta_add, .sta_remove openwifi_sta_remove, .set_channel openwifi_set_channel, .config_interface openwifi_config_interface, };这份 ops 表就是驱动与 mac80211 的协议契约。它涵盖了网卡生命周期管理和数据收发openwifi_start负责把 FPGA 从复位状态拉起来配置射频和 DMA 通道openwifi_tx是发送路径的入口会从sk_buff里取出 802.11 帧拷到 DMA 缓冲然后写门铃寄存器openwifi_config负责把 mac80211 下发的参数同步到 FPGA 寄存器。每一层都有自己的职责驱动层把 Linux 协议栈的“软件请求”翻译成 FPGA 的“寄存器操作”。提示想快速理解 openwifi 的软硬件边界不要从 RTL 开始读先打开驱动里的头文件把所有寄存器宏定义看一遍模块划分就一目了然了。3. 核心模块拆解从 DMA 搬运到 OFDM 空口的一条完整链路这一章是研究 openwifi 最核心的部分。看源码时我建议按数据流方向去追踪从 Linux 发送一个 ping 包开始这个包怎么变成 802.11 帧怎么进 DMA 描述符怎么被 FPGA 调制成 OFDM 符号最后从天线发出去。反向的接收路径也是一样。理解了这条链路才算真正读懂了这个项目。3.1 DMA 与双缓冲机制数据搬运的生命线openwifi 的数据通路核心在 DMA 引擎。因为 WiFi 帧速率高数据吞吐量远不是寄存器轮询能顶住的必须靠 DMA 批量搬运。驱动里维护了一个描述符环FPGA 通过硬件指针自动遍历这些描述符。关键设计是双缓冲一块 DMA 缓冲正在往系统内存搬运数据时另一块已经在接收新的帧避免乒乓等待。struct openwifi_dma_desc { u32 address; /* 物理地址指向数据缓冲 */ u32 length; /* 帧长度 */ u32 control; /* 控制位含方向与使能 */ u32 status; /* 硬件写回含完成标志与错误位 */ };每个描述符对应一块物理内存。address放的是物理地址因为是 FPGA DMA 直接访问不能用虚拟地址length是帧长驱动在提交前填入control里要置位 TX 使能或 RX 回收标志status由 FPGA 硬件写回驱动通过读它判断这一帧是否发送完成。丢包排查时重点看status里的错误位常见的是 FIFO 溢出或描述符未对齐。DMA 初始化时驱动要用dma_alloc_coherent分配一致性内存不能走普通kmalloc。缓存一致性处理不好CPU 写的数据 FPGA 读不到是这类驱动最常见的玄学问题。openwifi 的代码里发送路径会把sk_buff的内容拷进 DMA 缓冲再写门铃寄存器就是为了避免 skb 被释放后 DMA 还在访问导致的悬空指针。3.2 OFDM 收发链路从同步到星座解映射OFDM 是 802.11a/g/n 物理层的基础。openwifi 的 FPGA 工程里这条链路被拆成了清晰的功能级模块。发送侧数据先做加扰、BCC 卷积编码、交织然后映射成 QAM 符号插入导频经过 IFFT 变换到时域加上循环前缀和窗函数最后组帧发送。接收侧是逆过程分组检测、载波频率偏移校正、符号定时、FFT、信道估计、均衡、解映射、去交织、Viterbi 译码、解扰和 FCS 校验。// 示意基于 LSU 的前导检测用于分组到达检测 wire [31:0] corr_acc; wire pkt_detected; assign pkt_detected (corr_acc threshold) valid_gain; always (posedge clk) begin if (pkt_detected) begin state SYNC_ACQ; // 进入同步捕获状态 end end分组检测是接收机的第一关它用短训练序列的周期性做自相关累加。corr_acc是连续多个样本相关值的累加结果threshold要根据接收信号强度动态调整灵敏度设太低容易误触发设太高会漏掉弱信号这块需要根据自己的使用场景反复调。检测到分组后进入SYNC_ACQ状态才开始做细同步和信道估计。很多“能扫到 AP 但连不上”的现象问题就出在这个阈值和 AGC 设置的配合上。3.3 CSMA/CA 与 MAC 状态机实时性由硬件保证CSMA/CA 是 Wi-Fi 之所以能“多个设备共享信道”的关键。openwifi 没把它全放到 Linux 里做因为软件中断延迟抖动根本达不到时序要求。FPGA 内部实现了一个完整的 DCF 状态机包括帧间隙计时、竞争窗口、随机 backoff、NAV 维护。下面是一个简化行为描述方便理解 FPGA 内部逻辑// 伪代码FPGA 内 CSMA/CA 状态机行为 if (channel_busy) begin defer(); nav max(nav, receive_duration); end else if (nav 0) begin nav--; end else if (backoff 0) begin if (slot_boundary) backoff--; end else if (frame_pending) begin if (medium_idle_duration DIFS) begin transmit_frame(); backoff random(0, contention_window); end end这段行为逻辑表达了核心竞争机制信道忙时退避等待NAV 计时未结束前不发送backoff 计数器只有在信道空闲时才递减。contention window 会根据重传次数指数增长这是 802.11 标准对冲突避免的基本策略。openwifi 把这段逻辑固化在 FPGA 状态机里所以它能让多个节点在一个信道里公平共享。想在 MAC 层做定制开发的重点就是改这套状态机比如修改 backoff 上限、加入 TDMA 时隙调度之类。4. 把 openwifi 跑起来编译工程、加载驱动、三模式联调拿到源码之后最难的是把整套环境搭起来。openwifi 不是一个“克隆下来就能编译”的项目它依赖具体硬件平台、FPGA 工具链版本、Linux 内核版本和 AD9361 驱动。但按正确顺序来差不多一天能跑通。4.1 硬件准备与连接第一件事是确认开发板。openwifi 的目标平台是 Xilinx Zynq-7000 系列比较理想的是自带 AD9361 FMC 接口的板子。接好电源、串口、网线然后检查 FPGA 和 AD9361 之间的 SPI 与数据接口是否都正确接入。常见问题是用开发板原厂的 AD9361 配置程序把射频芯片初始化成和数据通路不匹配的模式。硬件作用注意事项Zynq 开发板运行 Linux FPGAPS 双核 A9配置内存 1GB 以上AD9361 子卡射频收发确认 FMC 接口供电和天线接入MicroSD 卡存放系统镜像建议 16GB 以上用于落盘USB 串口线控制台输出波特率通常 115200运行时一个很关键的配套是设备树。openwifi 的设备树节点要声明 FPGA 寄存器的物理地址范围、DMA 通道和中断号。如果设备树和实际综合出来的 bitstream 不一致最常见的现象是驱动 probe 成功但一收发就超时。这一步没有快速调试捷径老老实实对照 FPGA 工程里 AXI 地址分配表核对。4.2 用 Vivado 构建 FPGA 工程与 Linux 镜像openwifi 的源码里有一套完整的 FPGA 工程推荐用版本匹配的 Vivado 打开。高版本 Vivado 打开老工程时会有 IP 核升级提示别图省事直接点 OK容易把相对引脚约束和时序约束弄乱。建议全部保持原始配置只重新综合实现。# 进入 FPGA 工程目录用 TCL 模式构建 vivado -mode batch -source ./scripts/openwifi_build.tcl这条命令会做综合、实现、生成 bitstream最后输出下载文件。构建时间取决于机器性能通常半小时到一小时。构建完成后把 bitstream 转成 BOOT.BIN 所需的格式连同编译好的内核镜像和设备树一起放进 SD 卡。这里有个踩坑教训SD 卡里的设备树必须和内核编译时的设备树保持一致但很多人更新了内核后忘了同步设备树导致各种神秘驱动问题。4.3 启动 Linux 并加载 openwifi 驱动板子启动进系统后先确认 FPGA 已加载然后手动加载驱动模块不要依赖自动加载不然出错时日志混在启动流程里不好排查。# 加载 FPGA bitstream不同板子工具可能不一样 fpgautil -b /lib/firmware/openwifi_main.bin # 等待驱动模块加载 modprobe openwifi dmesg | grep -i openwifi成功加载后dmesg里通常能看到 openwifi 相关的中断注册和 DMA 初始化日志。然后是扫描无线网络验证射频链路正常ip link set dev wlan0 up iw dev wlan0 scan | head -20能扫描到周围 AP说明接收路径和数据通路基本通了。如果这里一条都看不到先回到 2.2 节检查 AD9361 接口射频问题占八成。4.4 AP、STA、monitor 三种模式验证openwifi 最爽的一点是三种网络角色都能跑。作为 AP 时用 hostapd 管理框架hostapd /etc/hostapd-openwifi.conf典型配置里包含ssidopenwifi-test、interfacewlan0、drivernl80211等字段。启动后另一台设备能搜到该 SSID就能确认发射链路正常。作为 STA用wpa_supplicant连接外部 AP日志里出现4-way handshake completed就是成功。而 monitor 模式是研究 802.11 协议最实用的武器ip link set wlan0 down iw dev wlan0 set type monitor ip link set wlan0 up tcpdump -i wlan0 -nn -emonitor 模式下能看到完整 MAC 头、管理帧的 beacon 和 probe request以及每个帧的 RSSI 和速率。做协议分析和二次开发基本都离不开这个模式。5. 避坑与常见问题排查openwifi 实践中的高频故障跑 openwifi 的过程本质上就是和各种软硬件边界问题搏斗的过程。以下几条是实操中最容易踩的坑每一条都有明确的现象、原因和解决路径。5.1 驱动加载成功但 wlan0 不出现现象modprobe openwifi无报错但ip link里没有 wlan0。原因是 FPGA 的 bitstream 没有正确加载或者设备树节点地址与 FPGA 实际地址不匹配。排查时先看dmesg最前面有没有 FPGA manager 的失败日志很多原因是 fpgautil 没指定准确路径或 bit 文件格式不对。解决方法是单独手动执行 bitstream 加载再回读 FPGA 寄存器确认 ID。5.2 发送队列不完成DMA 一直超时现象能扫描但 ping 不通iw dev wlan0 station dump能看到关联但驱动日志里频繁报 DMA 超时。原因是中断没有真正连到 Linux 处理程序或者 IP 核的中断号配置错误。我一般会先检查设备树里 interrupt 属性和 FPGA 工程里中断控制器连接是否一致。解决方法是先简化场景关掉 802.11 加密和 QoS只保留数据帧重发。5.3 能连上但吞吐量极低速率一直最低档现象AP 连接成功但 iperf 测速只有几 Mbpsiw dev wlan0 link里速率停在 6 Mbps 或 1 Mbps。原因是接收端的信噪比评估偏低或者发送端没有使能高 MCS 速率。优先检查天线接口是不是松了再检查 AGC 配置是否把增益压得太死。解决方法是把信道带宽换成 20 MHz先确认基础链路再渐进提高 MCS 档位。5.4 Vivado 重新编译后驱动寄存器映射错乱现象同一个工程别人编译能跑你编译完所有寄存器读写都不正常。原因几乎都是项目里的 IP 核版本被 Vivado 自动升级了导致地址映射变化。解决方法是严格用仓库锁定的 Vivado 版本构建过程不点交互式升级按钮批次完成后做一次寄存器自检确保读 ID 寄存器返回预期值。注意openwifi 的工程对工具链版本比较敏感新版本 Vivado 的 IP 核升级会悄悄改掉时序约束如果发现跑起来性能差很多先回溯工具链版本。6. 二次开发从一个 PHY 参数开始改出你自己想要的无线链路openwifi 的改造空间非常大。最常做的是改物理层参数比如调制方式、带宽和发射功率。以修改信道带宽为例20 MHz 和 5 MHz 之间不只是射频 LO 改一下还涉及 FPGA 里 OFDM 参数、采样率和滤波器系数的联动。常见做法是先把参数抽象成寄存器让 Linux 在openwifi_config回调里下发static int openwifi_config(struct ieee80211_hw *hw, u32 changed) { struct openwifi_local *local hw-priv; if (changed IEEE80211_CONF_CHANGE_CHANNEL) { // 将带宽和中心频率写入 FPGA 寄存器 fpga_write(local, REG_BW_MODE, local-hw-conf.chandef.width); fpga_write(local, REG_CENTER_FREQ, local-hw-conf.chandef.chan-center_freq); } return 0; }这样的好处是把协议栈的参数请求转化成 FPGA 可识别的寄存器值中间加一点自己的业务逻辑也非常顺手。改完参数后验证路径先用 monitor 模式抓自己发出的帧确认 beacon 帧里的 HT Capabilities 符合预期再用频谱仪或另一台设备收包验证。检查 PHY 层细节时openwifi 提供了内部调试接口可以直接读取 FPGA 内部信号这个能力是普通商用网卡不具备的。我个人的习惯是改完任何一个参数都先用固定信道模式发一段时间数据再用实时频谱观察发射频谱形状确认没有频谱再生或杂散超标。这套流程走顺以后做无线协议实验就像写应用层代码一样方便。用 openwifi你可以把物理层协议栈随意修改然后马上在真实环境中验证这在传统 WiFi 芯片上是完全做不到的。希望这篇拆解能让你在拿到源码后少走弯路帮你把更多精力花在真正想做的功能上。本文还有配套的精品资源点击获取