省流版先说结论:这套环境我前后配了两次,第一次纯靠apt装二进制包,省心但gr-ieee802-11死活跑不起来;第二次老老实实从源码编UHD、VOLK、GNU Radio,再把WiFi收发模块gr-ieee802-11接上,虽然中间踩了五六个坑,但最后USRP能收能发,802.11抓包和注入都通了。这篇文章就是第二次编译全过程的还原,偏记录向,但每一步的命令、CMake参数、报错解法都写清楚了,适合想在自己Ubuntu 22.04上折腾SDR开发环境的哥们参考。
1. 内容整体设计与思路拆解
先把这次要编译的几个东西串一遍。GNU Radio是一个开源软件无线电生态,核心是基于Python和C++的流图开发框架,能让你用积木式模块搭建信号处理流程。UHD(USRP Hardware Driver)是Ettus公司USRP系列硬件的驱动库,负责上位机和射频前端之间的通信。VOLK(Vector-Optimized Library of Kernels)则是一个底层SIMD优化库,GNU Radio的很多信号处理模块在运行时都会调度VOLK里的高性能内核,相当于给计算密集型模块加速用的。
而gr-ieee802-11这个模块,是GitHub上开源的IEEE 802.11b/g/n收发信机实现,简单说就是让USRP能发射和接收WiFi信号。它依赖新版GNU Radio和UHD,但问题在于它一直在跟着GNU Radio主线走,如果你系统里装的是Ubuntu源里自带的GNU Radio 3.10.1.1,那大概率会遇到API接口不匹配的问题,比如gnuradio::pmx消息接口变了、boost::shared_ptr换成了std::shared_ptr等。
所以这次我的整体编译路线是:UHD 4.5.0 → VOLK 3.1.0 → GNU Radio 3.10.5.1 → gr-ieee802-11最新master分支。为什么要这个顺序?因为VOLK虽然是独立项目,但GNU Radio编译时会去检测它,UHD也会被GNU Radio的gr-uhd模块依赖,而gr-ieee802-11又同时依赖GNU Radio的runtime和UHD的device API,所以必须自底向上先把基础层编干净。
有人可能会问,GNU Radio和UHD直接apt install不就行了吗?我明确说,如果你只跑GNU Radio自带的那些模块,比如信号源、滤波器、FFT、星座图,那apt包完全够用,而且省时间。但gr-ieee802-11是个比较挑环境的模块——它用到了一些较新的meson构建逻辑、C++17标准、以及UHD 4.x新版API,Ubuntu 22.04源里的GNU Radio和UHD版本偏旧,我实测直接编译gr-ieee802-11会报UHD version not found之类的问题。所以从源码编译是绕不开的路。
1.1 为什么选这几个版本而不是最新版
版本选择上,我没有盲目追新。UHD目前官方稳定版已经到了4.6甚至4.8,但gr-ieee802-11的CMakeLists.txt里对UHD版本有范围要求,某些新版本里移除了旧API,反而会编不过。我最终选了4.5.0,这个版本在USRP X310和B210上表现都很稳,而且和GNU Radio 3.10.5.1配合良好。
同理,GNU Radio我没有选3.11或者main分支,因为gr-ieee802-11虽然紧跟主线,但难免有几天跟不上的情况,与其赌它适配最新版,不如选一个生态里验证比较多的3.10系列。VOLK选3.1.0也是因为它是GNU Radio 3.10.5.1发布时配套测试过的版本,没必要为了“更新”而更新。
1.2 一次编译涉及的核心模块关系
我自己画了个依赖图在脑子里:VOLK在最底层,它只依赖libboost和libcpu相关特性检测,主要提供各种SIMD内核。UHD在中间层,它不依赖GNU Radio,但依赖Boost、libusb、Python3等。GNU Radio最上面,它同时依赖VOLK和UHD(如果要gr-uhd模块的话)。gr-ieee802-11又在GNU Radio之上,依赖GNU Radio的runtime、qtgui、network等模块,并在运行时通过UHD的API控制USRP。
这个依赖关系决定了编译顺序和排错方向,后面遇到链接错误的时候,基本就能定位是哪一层出了问题。
2. 核心细节解析与实操要点
2.1 安装基础依赖包
这一步很多人会忽略,直接跳过去源码编译,然后卡在各种各样的头文件缺失上。实际上Ubuntu 22.04下,先花10分钟把依赖包装齐,后面能省一小时查错的时间。
我在干净系统上执行的是:
sudo apt update sudo apt install -y \ build-essential cmake git pkg-config \ libboost-all-dev libgmp-dev libmpfr-dev \ libfftw3-dev libgsl-dev libsdl1.2-dev \ libqt5opengl5-dev qtbase5-dev libqwt-qt5-dev \ liblog4cpp5-dev libgtest-dev libxml2-dev \ python3-dev python3-pip python3-numpy python3-mako \ python3-six python3-lxml python3-setuptools \ swig doxygen graphviz \ libusb-1.0-0-dev libusb-dev \ libcomedi-dev libcppunit-dev \ libsndfile1-dev libasound2-dev \ libblas-dev liblapack-dev \ libcurl4-openssl-dev libudev-dev \ python3-pyqt5 python3-qwt \ portaudio19-dev pavucontrol \ python3-scipy python3-matplotlib \ libi2c-dev libspdlog-dev这里面有几个值得解释一下。libvolk-dev和libvolk2-dev这两个我故意没装,因为后面要自己编译VOLK,装系统版会导致头文件冲突,这是血的教训。libboost-all-dev是必须的,UHD和GNU Radio都会用Boost的shared_ptr、thread、program_options等库,不装全后面会有一堆链接错误。libqwt-qt5-dev和qtbase5-dev是用来编译GNU Radio的Qt GUI模块的,如果你想看星座图、频谱图,这几个包缺一不可。
Python方面我用了系统默认的Python 3.10。这里有个点要注意:不要手贱装conda或者pyenv把自己系统的Python替换掉,GNU Radio 3.10的gr-modtool和gnuradio-companion对系统Python路径和site-packages路径有硬编码,一旦Python路径变了,各种import不了模块的问题会接踵而至。
2.2 为什么不建议用pip安装GNU Radio
网上有些教程会让你直接用pip install gnuradio,或者用conda install -c conda-forge gnuradio,这两个方案在实际做SDR开发时都不太推荐。conda版本的好处是简单,但问题在于conda会创建一套隔离的Python环境,你后面自己编译的gr-ieee802-11模块如果绑定的是系统Python,那就要想办法把模块装进conda的site-packages里,非常折腾,而且一旦conda更新了包,版本又对不上了。
pip装GNU Radio更是坑,因为它本质上就是调conda-forge的库,pip会把一堆二进制包拉进来,看起来能用,但和usb设备配合时,UHD的固件刷写路径、pybind11生成的Python绑定路径全混乱,最后你会发现自己连USRP的固件都刷不进去。所以,自己编译虽然费时间,但路径清晰,模块可控,后续加新模块也方便。
3. 实操过程与核心环节实现
3.1 编译UHD 4.5.0
UHD是整个链路的基石,如果它不稳,后面的东西全白搭。我先从GitHub拉取代码,然后签出4.5.0标签:
git clone --recursive https://github.com/EttusResearch/uhd.git cd uhd git checkout v4.5.0编译之前有个重要的准备步骤,把UHD的Python绑定打开。UHD的Python API对于gr-ieee802-11不是硬依赖,但开发调试时会用到uhd.usrp.MultiUSRP来写简单的收发脚本,所以我还是把ENABLE_PYTHON_API设置为ON。
具体编译命令:
mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr/local \ -DENABLE_PYTHON_API=ON \ -DPYTHON_EXECUTABLE=$(which python3) \ -DENABLE_STATIC_LIBS=OFF \ -DCMAKE_BUILD_TYPE=Release \ .. make -j$(nproc) sudo make install sudo ldconfig这里要说明一下为什么CMAKE_INSTALL_PREFIX用/usr/local而不是默认的/usr。如果用/usr/local,后续GNU Radio在编译时检测UHD头文件会去/usr/local/include里找,而Ubuntu的apt包如果也装了UHD,两个路径会打架。我的经验是:要么就完全不用apt装UHD,要么统一默认路径。为了省心,我卸载了所有libuhd相关的包,然后直接用/usr/local。
编译完成后,建议运行一下:
uhd_find_devices如果提示No UHD Devices Found,别慌,这是正常的,因为还没插USRP。但至少说明库文件安装没问题,Python绑定也能正常加载。
对于开发者来说,还有一个值得检查的点是UHD的固件路径。默认情况下,UHD会从/usr/local/share/uhd/images下寻找fpga固件。刷固件的时候,用uhd_image_loader命令。
3.2 编译VOLK
VOLK编译其实是最顺的,它本身不带太多外部依赖,只要Boost和Python环境干净就行。
git clone --recursive https://github.com/gnuradio/volk.git cd volk git checkout v3.1.0 mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_BUILD_TYPE=Release \ -DPYTHON_EXECUTABLE=$(which python3) \ .. make -j$(nproc) sudo make install sudo ldconfig这里我用了一个技巧:编译之前先跑一下volk_profile工具。它会在当前用户目录下生成.volk_profile文件,记录当前CPU上各个VOLK内核的实测性能数据。这样后面GNU Radio运行时能更精准地选最快的SIMD实现,尤其在WiFi这种高吞吐场景下,性能差别是能感知到的。
VOLK编译完之后,可以做个快速验证:
python3 -c "import volk; print(volk.get_power_of_volk())"能正确输出版本和CPU特性就说明没问题。
3.3 编译GNU Radio 3.10.5.1
GNU Radio编译是整个过程中最花时间的一步,单机8核编译大概要30-40分钟。它的大头在于grc(GNU Radio Companion)、gr-qtgui、gr-uhd这几个模块的编译,尤其是Python绑定那一层。
源码获取:
git clone --recursive https://github.com/gnuradio/gnuradio.git cd gnuradio git checkout v3.10.5.1然后建立build目录编译:
mkdir build && cd build cmake -DCMAKE_INSTALL_PREFIX=/usr/local \ -DCMAKE_BUILD_TYPE=Release \ -DPYTHON_EXECUTABLE=$(which python3) \ -DENABLE_DEFAULT=ON \ -DENABLE_GR_UHD=ON \ -DENABLE_GR_QTGUI=ON \ -DENABLE_GR_NETWORK=ON \ -DENABLE_GRC=ON \ -DENABLE_PYTHON=ON \ -DENABLE_DOXYGEN=OFF \ -DENABLE_TESTING=OFF \ .. make -j$(nproc) sudo make install sudo ldconfig注意,这里有个关键点:ENABLE_GR_NETWORK要设为ON。gr-ieee802-11的某些示例用到了UDP收发模块,如果network模块没编译,后面跑示例的时候会提示找不到gr::network::udp_sink。我第一次编译时就是没开这个选项,导致后续示例脚本老报错,排查了半天。
另外一个容易被忽略的是swig和pybind11的关系。GNU Radio 3.10从默认绑定方式从SWIG切换到了pybind11,但某些模块,如gr-qtgui,仍然依赖pybind11生成Python绑定。所以编译前确认系统里有pybind11-dev,并且版本最好是2.10以上,这个在apt里一般都有。
编译完成后,验证:
gnuradio-config-info --version python3 -c "import gnuradio; print(gnuradio.version)"如果能正常显示3.10.5.1,那说明主体OK。再启动一下gnuradio-companion看看能不能正常打开图形界面。
3.4 编译gr-ieee802-11模块
这是本次编译的重头戏。gr-ieee802-11是一个外部的out-of-tree模块,它不在GNU Radio主仓库里,需要单独拉取。
git clone https://github.com/gr-ieee802-11/gr-ieee802-11.git cd gr-ieee802-11这个模块没有像GNU Radio那样用CMake默认配置,而是用了Meson构建系统。这也是一个坑点:很多人拿着cmake的习惯去编译它,结果发现没有CMakeLists.txt,直接懵了。它的构建文件是meson.build。
编译过程:
meson setup build --prefix=/usr/local ninja -C build sudo ninja -C build install sudo ldconfig这里有一个细节:Meson构建的时候,会自动检测GNU Radio的安装路径。因为我前面把GNU Radio装到了/usr/local,所以这里没额外指定pkg-config路径。如果你安装到其他自定义路径,就需要设置PKG_CONFIG_PATH环境变量:
export PKG_CONFIG_PATH=/your/prefix/lib/pkgconfig:$PKG_CONFIG_PATH编译完成后,模块的Python绑定会装到/usr/local/lib/python3.10/site-packages/,这时候你需要在运行Python之前让它能找到gr-ieee802-11的模块。
可以把以下变量写进~/.bashrc里,省得每次都设置:
export PYTHONPATH=/usr/local/lib/python3.10/site-packages:$PYTHONPATH export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH验证模块是否导入成功:
python3 -c "import gr_ieee802_11; print('ok')"不过这里说句实话,gr-ieee802-11的模块名在不同版本有变化,有的版本是import gr_ieee802_11,有的版本是from gnuradio import ieee802_11。建议编译完先去Python里pkgutil.iter_modules()搜一下,找到正确的模块名再导入,不然会被ModuleNotFoundError搞得很烦。
3.5 WiFi收发功能验证流程
编译安装完成后,最关心的肯定是能不能真的收WiFi信号。这里我把验证方法也说一下。
gr-ieee802-11仓库的examples目录里带了一些示例脚本,比如wifi_phy_hier.py和wifi_loopback.py,但最典型的是接收脚本,一般叫wifi_rx.py。
实测如果要抓WiFi Beacon帧,可以这样跑:
python3 examples/wifi_rx.py --freq=2.437G --rate=20M --args="type=b200"参数里freq是中心频率,2.437G是WiFi 2.4G频段的6信道;rate是采样率,20M是WiFi 20MHz带宽的采样率;args指定USRP设备类型,B200就写type=b200,X310就写type=x300。
如果一切正常,脚本起来之后,会打开一个Qt GUI窗口,里面有星座图、FFT频谱、解调后的比特流输出。把手机开个热点,或者拿路由器放旁边,只要USRP的接收频率对准了,就能在终端看到“Captured frame”之类的打印,Qt窗口里也会出现明显的802.11信号特征。
我实测下来,B210在2.4GHz频段收Beacon是最轻松的,因为Beacon是广播帧,不需要配对,只要信号强度够就能抓到。用X310也试过,但X310是外接时钟和网口的,配置复杂一些,对新手不太友好。
发射的话,可以用wifi_tx.py,同样指定freq、rate、args,它会周期性发送特定的WiFi帧。需要注意的是,发射时一定要接好天线,不然反射功率过大容易烧前端。USRP的前端没有做内置的收发切换保护,这一点和真实WiFi网卡不太一样,所以用的时候要注意。
4. 常见问题与排查技巧实录
4.1 编译gr-ieee802-11时提示找不到gnuradio/block_api.h
这个报错几乎每个人都遇到过,它本质是gr-ieee802-11在找GNU Radio头文件时,去的是系统默认路径,而我的GNU Radio安装在/usr/local。解决办法很简单,确认pkg-config路径:
export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/local/lib/python3.10/site-packages/pkgconfig:$PKG_CONFIG_PATH然后再跑meson setup重新生成build配置。
4.2 运行示例时提示ModuleNotFoundError: No module named 'gnuradio.blocks'
这个问题的根源是GNU Radio的Python绑定没有装到Python搜索路径里。处理方法是检查/usr/local/lib/python3.10/site-packages目录里有没有gnuradio目录。如果有,那就是PYTHONPATH没设好;如果没有,说明编译时ENABLE_PYTHON=OFF,需要重新编译GNU Radio。
4.3 UHD连接B210时报USB 3.0带宽不够
这个坑我见过不少。B210虽然是USB 3.0接口,但很多廉价USB hub或者劣质数据线只能跑到USB 2.0的速度,导致采样率一超过10M就会丢包或者报Bandwidth Insufficient。换根高质量的数据线、直接插主板USB 3.0口(蓝色口)、关掉省电模式,基本能解决。
4.4 VOLK编译时报找不到vc_compiler
这是VOLK的新版对编译器版本有要求。Ubuntu 22.04自带GCC 11,如果不满足,会报这个错。解决办法是装个GCC-12或者用clang编译。我实测用clang编译VOLK完全没问题,而且性能也很好。
5. 调试进阶:怎么判断链路是否健康
编译安装都通过之后,别急着跑WiFi收发,先做几个健康检查。
第一个检查是UHD时间同步。USRP设备内部有FPGA时钟,如果时间源不锁定,收发会严重错位,表现为接收的星座图旋转跳跃。用以下命令看:
uhd_usrp_probe看输出的clock locked状态。如果是internal时间源且显示OK,说明没问题;如果是external参考时钟,要确认参考信号接入了。
第二个检查是VOLK性能优化。运行volk_profile让它自动测一遍当前平台的最佳内核,因为我发现很多编译报错或者性能低下的问题,说到底都是VOLK没有针对性优化。跑完之后GNU Radio的FFT、滤波器这些模块会自动选用最合适的内核,WiFi接收的性能能明显提升。
第三个检查是GRC图能不能正确加载gr-ieee802-11的block。打开GNU Radio Companion,在block列表里搜ieee802_11,如果能找到wifi_mac、wifi_phy等模块,说明模块注册成功,Python绑定也正确。
不过说实话,GRC里虽然能看到这些模块,但gr-ieee802-11的完整收发流程更多还是靠运行examples目录里的Python脚本,因为WiFi的MAC层状态机比较复杂,GRC画流图反而不直观。我自己是先用脚本调通,再用GRC做简单验证。
6. 性能优化的一些体会
WiFi信号处理对计算资源的要求比较高。我用B210接收20MHz带宽的WiFi信号,跑grc流图时CPU占用能到70%到80%。如果电脑比较老,建议从这三方面优化。
第一,把GNU Radio的block affinity设置到不同CPU核心上。默认情况下,所有block都在一个进程里跑,GNU Radio的调度器会把它们分配到同一个核心组,需要手动指定affinity。比如在GRC的Options里设置thread affinity,或者Python脚本里调用tb.set_affinity()。这个优化对多核机器效果明显。
第二,块大小(samples per symbol)调大。WiFi接收机里很多模块的块大小默认是几千个采样,如果调整到一万以上,可以减少调度次数,吞吐量能提升不少。
第三,USRP接收增益要适中。我发现很多人喜欢把tx_gain和rx_gain调到最大,其实增益太高会导致接收机饱和,星座图一团糊。802.11信号本身接收灵敏度很高,我通常把B210的rx_gain设置到20-25dB就足够了,除非信号特别弱再往上加。
7. 写在最后的几个避坑心得
编译这套环境,整体下来不算难,但坑确实不少。总结几条实操心得,希望能给后来的朋友省点时间。
第一,严格按顺序编译,不要跳步。先VOLK、再UHD、再GNU Radio、最后gr-ieee802-11,这个顺序是铁律。颠倒顺序会导致依赖检测失败,白白浪费时间。
第二,版本锁定要早做。GNU Radio 3.10.x和UHD 4.5.x不是随便组合都能出的。如果你想换版本,先把依赖矩阵查清楚,再动手。
第三,不要混用包管理器安装的库和自己编译的库。我第二次编译时,故意把apt的libuhd、libvolk、gnuradio全部卸载干净,才避免了很多奇怪的运行时报错。
第四,这个环境搞完之后,强烈建议写一个安装脚本记录所有步骤。因为过一段时间你可能会在另一台机器上重新配环境,到时候照着脚本走,能省大量试错时间。我现在这台机器就是直接跑当初的脚本恢复的环境,半小时全部搞定。
第五,如果只是做WiFi抓包分析而不打算深度定制信号处理,不如直接用现成的软件方案,比如Wireshark加普通WiFi网卡的monitor模式就能完成很多工作。gr-ieee802-11的价值在于你可以完整控制物理层调制方式和MAC帧结构,适合做协议研究和原型验证,追求的是自由度而不是简单易用。想清楚自己的需求,再决定要不要投入时间去编译这一整套环境。