收到两块板卡,一台是USRP N310,一台是USRP B210,拆封后我做的第一件事不是急着写收发程序,而是先用UHD库把设备基础状态完整摸一遍。很多人拿到板卡就直接开始采数、发波形,遇到连接不稳和异常数据时才回头查驱动,结果调试周期被拉得很长。我这些年和UHD打交道的习惯是:先花十分钟做基础测试,确认枚举、时钟、通道、链路都没有问题,再进入正式开发。这篇文章就把我用UHD库对USRP N310/B210做基础测试的整套流程拆开,覆盖环境准备、设备枚举、射频回环、状态诊断和自动化脚本。不管是刚拿到第一块板子的初学者,还是需要验收大量设备的工程师,基本都能照着这套思路走一遍。
1. 环境准备与设备枚举:先让驱动看到你的板卡
1.1 安装UHD库与固件镜像的版本匹配
我在Linux下面做UHD开发用得最多,所以环境部分就以Ubuntu/Debian系为例子。安装UHD库通常两条命令,一条装命令行工具和头文件,一条装Python绑定:
sudo apt install uhd-host libuhd-dev pip install uhd这里要注意,pip install uhd装的是UHD的Python API,它依赖系统里已经存在的libuhd动态库。如果你在虚拟环境里装,记得确认这个Python绑定版本和系统UHD版本能对上。版本差太多时,经常出现Python API能导入但一创建设备就报错的情况,排查起来很费时间。
装完UHD之后,必须同步准备固件和FPGA镜象。我一般先跑一下官方镜像下载工具:
uhd_images_downloader这个工具会把板卡所需的固件、FPGA镜像、甚至部分校准数据下载到本地缓存目录。不要跳过这一步。FPGA镜像版本和UHD版本不匹配时,uhd_find_devices虽然能看到设备,但后续创建收发流时会报版本不兼容或固件加载失败。
另外还有sudo权限的问题。B210这类USB设备默认只有root能直接访问,官方安装包通常会附带一份udev规则(常见的是89-ettus.rules),把当前用户加入plugdev用户组后,非root用户也能用:
sudo usermod -aG plugdev $USER改完用户组记得注销重新登录。我建议所有测试都在普通用户下进行,避免sudo模式下UHD缓存路径和权限不一致带来的干扰。
1.2 uhd_find_devices:从输出里读出的关键信息
设备连接好、环境就绪后,第一步就是枚举设备。命令行很简单:
uhd_find_devices正常情况输出类似下面这样:
[INFO] [UHD] linux; GNU C++ version 11.4.0; Boost_108100; UHD_4.6.0.head -------------------------------------------------- -- UHD Device 0 -------------------------------------------------- Device Address: type: n3xx addr: 192.168.10.2 product: N310 serial: ABC123B210则会是:
Device Address: type: b200 product: B210 serial: DEF456我比较关注三个字段:type、product、serial。type决定了后续所有参数里设备类型怎么写,product确认板卡型号和标签是否一致,serial则是多板卡同时接入时最可靠的区分依据。如果你的机器上插了多块USRP,uhd_find_devices --args="type=b200"或者uhd_find_devices --args="addr=192.168.10.2"可以缩小范围,只枚举指定地址或指定类型的设备。
N310是网络设备,它走的是10G以太网口,所以addr是IP地址。出厂默认IP通常是192.168.10.2,具体看板卡设置,我在测试前会先确认主机网口和板卡在同一个子网,最常见的问题就是主机IP写错或者板卡被前一手设置过别的网段。B210则是USB 3.0直连,理论上插上就能被UHD识别,如果你发现枚举不到,第一步先换根线、换个USB口,99%的枚举失败都是物理链路引起的。
1.3 uhd_usrp_probe:一层一层地把硬件信息打开
枚举成功之后,接下来做硬件探查:
uhd_usrp_probe如果有多块板卡,可以加参数精确指定:
uhd_usrp_probe --args="type=n3xx,addr=192.168.10.2"uhd_usrp_probe的输出比find_devices细致得多,它会从主板信息(mboard)、射频子板(subdev)、到每个通道的传感器和可用前端全部列出来。我通常会重点看这几项:
一是mboard部分里的FPGA版本信息和固件版本,确认和UHD库满足兼容关系;二是通道数量,B210是2发2收,N310是4发4收,如果probe输出里显示通道数不对,多半是镜像加载失败或者设备本身有问题;三是每个通道支持的频率范围和增益范围,这里能快速确认板卡射频前端工作状态是否正常。
uhd_usrp_probe跑通,一次不报错,说明主机和板卡之间驱动层的握手已经完成。这也是后面所有测试的基础,我个人的习惯是:probe没有干净通过之前,坚决不碰上层应用。
2. 射频链路回环测试:单音收发与FFT验证
2.1 为什么第一个测试要做射频回环
驱动层握手成功,不代表射频链路就是通的。最直接的验证方式就是回环:把板子的发射端口和接收端口用射频线连起来,发射端产生一个已知信号,接收端把收到的信号抓回来分析。如果发射的信号能在接收端被正确解调出来,说明本振设置、上下变频、增益控制、ADC/DAC采样链路、基带到主机的数据通路全都是好的。
回环也有讲究。最外部是射频线缆回环,信号经过真实射频路径从TX口回到RX口;内部还有数字回环、模拟基带回环等模式。对验收测试来讲,我建议优先做射频线缆回环,因为它是唯一能同时验证板卡外部的物理接口和内部射频链路的方案,也更接近真实使用场景。至于板卡是否支持内部回环、怎么配置,需要参考对应的官方手册,不要想当然在代码里设置,否则有可能把一个芯片内部的自测通道误当成完整信号链路。
2.2 用UHD Python API实现单音回环测试
我用Python比较多,下面这段是我日常做单音回环测试的简化版本。它的作用是:在2.45GHz中心频率旁边偏1MHz处发射一个单音,接收端把信号抓下来,用FFT确认这个1MHz偏置的单音确实出现在接收频谱里。
import uhd import numpy as np import time CENTER_FREQ = 2.45e9 TONE_OFFSET = 1e6 RATE = 25e6 DURATION = 0.2 TX_GAIN = 30 RX_GAIN = 20 usrp = uhd.usrp.MultiUSRP("type=b200") usrp.set_tx_freq(CENTER_FREQ) usrp.set_rx_freq(CENTER_FREQ) usrp.set_tx_gain(TX_GAIN) usrp.set_rx_gain(RX_GAIN) usrp.set_tx_rate(RATE) usrp.set_rx_rate(RATE) num_samples = int(RATE * DURATION) t = np.arange(num_samples) / RATE tx_wave = np.exp(2j * np.pi * TONE_OFFSET * t).astype(np.complex64) # 建议先把发射buffer提前准备好 tx_buffer = np.zeros(num_samples, dtype=np.complex64) rx_buffer = np.zeros(num_samples, dtype=np.complex64) tx_streamer = usrp.get_tx_streamer(1, 2, [0]) rx_streamer = usrp.get_rx_streamer(1, 2, [0]) # 发射单音 tx_buffer[:len(tx_wave)] = tx_wave tx_streamer.send(tx_buffer, 1.0) # 接收回环信号 rx_streamer.recv(rx_buffer, 0.1) spectrum = np.abs(np.fft.fftshift(np.fft.fft(rx_buffer)))**2 freqs = np.fft.fftshift(np.fft.fftfreq(num_samples, 1/RATE)) peak_idx = np.argmax(spectrum) peak_freq = freqs[peak_idx] print(f"峰值频率: {peak_freq / 1e6:.2f} MHz") print(f"期望频率: {TONE_OFFSET / 1e6:.2f} MHz")这段代码有几个细节值得注意。第一,我习惯先准备一块完整的tx_buffer再发送,避免在流式传输中反复生成波形导致时序抖动。第二,get_tx_streamer(1, 2, [0])中第一个参数是流中通道数,第二个参数是每个通道的buffer大小倍数,第三个是实际使用的通道列表。不同UHD版本的API签名略有差异,参数顺序最好查一下当前版本的帮助。第三,接收之后立刻做FFT并打印峰值频率,这是判断回环是否成功的核心依据。
实际执行时,如果射频线缆接触不良或者收发增益设置不合适,FFT的峰值可能出现在其他频率,或者峰值不明显。我的经验是先把RX增益适当调低,避免接收饱和,再逐步增加TX增益,观察峰值是否跟着抬升。如果峰值频率能稳定落在发射设置的偏移位置上,说明从DAC到ADC这一整条链路是通的。
2.3 命令行快速验收与文件回放
Python脚本适合自动化,但现场快速验收时,我更喜欢直接用命令行工具。UHD自带整套命令行小工具,比如用uhd_rx_cfile抓一段IQ数据存文件:
uhd_rx_cfile --args="type=b200" --freq 2.45e9 --rate 25e6 --gain 20 --duration 0.2 rx_2p45g.dat发射端可以再用uhd_tx_cfile或者更简单的程序发连续单音。抓完数据后,用Python读取二进制文件并做FFT:
import numpy as np samples = np.fromfile("rx_2p45g.dat", dtype=np.complex64) spectrum = np.abs(np.fft.fftshift(np.fft.fft(samples)))**2 freqs = np.fft.fftshift(np.fft.fftfreq(len(samples), 1/25e6)) print(freqs[np.argmax(spectrum)])命令行工具的好处是不需要写完整程序就能验证链路,适合在出问题时快速定位是应用层的问题还是板卡链路的问题。
3. N310和B210一起测:接口、时钟、采样率边界别混着来
3.1 设备类型参数必须写对
当测试环境里同时存在N310和B210时,第一个容易翻车的地方就是MultiUSRP创建时传的device参数。很多人直接复用别人的代码,只改IP不改type,导致UHD用错误的类型去解析设备参数。
B210是USB设备,创建时要这样写:
b210 = uhd.usrp.MultiUSRP("type=b200")N310是网络设备:
n310 = uhd.usrp.MultiUSRP("type=n3xx,addr=192.168.10.2")如果两块板子同时要被一个进程管理,可以在一个UHD session里创建多个MultiUSRP对象,各写各的参数。不要在一个args字符串里尝试合并两种设备,UHD的session初始化是按device address逐个解析的,混写很容易报错。
3.2 时钟源和触发方式的配置差异
B210和N310在时钟同步方面的能力差异很大。B210一般用内部参考时钟,后面板也有外部10MHz参考输入接口,但默认状态下它只是自由运行,如果需要多设备协同,就必须把外部10MHz参考和1PPS信号同时送到板卡上。
N310的同步能力要强一档,板载了GPSDO和多种时钟参考模式。测试时我通常会这样设置:
n310.set_clock_source("external") n310.set_time_source("external")然后检查时钟是否锁定:
ref_locked = n310.get_mboard_sensor("ref_locked") if not ref_locked.is_valid() or not ref_locked.to_bool(): raise RuntimeError("参考时钟未锁定")基础测试阶段,至少要把ref_locked验证到true。如果用的是GPSDO还可以检查gps_locked和pps_locked。我在多设备测试中遇到过不少定位了很久才发现问题是参考时钟没分配到位的情况,所以现在每次搭建环境,第一步一定是确认每台设备的时钟状态传感器。
3.3 采样率上限与通道数的实测差异
B210和N310在业务定位上差距不小,基础测试时不能拿同一套采样率配置硬套。
| 对比项 | B210 | N310 |
|---|---|---|
| 连接接口 | USB 3.0 | 双10GbE SFP+ |
| 通道数 | 2发2收 | 4发4收 |
| 典型频率覆盖 | 70 MHz - 6 GHz | 10 MHz - 6 GHz |
| 基带采样率边界 | 最高61.44 MSPS,双通道时受USB带宽限制 | 受以太网链路和配置决定,可明显高于B210 |
| 同步能力 | 外部10MHz/PPS可选 | GPSDO、外部10MHz/PPS |
| 散热 | 被动散热 | 内置风扇 |
B210我通常建议单通道测试先跑到25 MSPS验证链路,再按需往高调;如果双通道同时满速率收发,USB 3.0带宽会变得比较紧张,容易出现overflow警告。N310的网络链路相对从容一些,但配套的网卡和交换机的数据通路规划仍然很重要,不稳定的万兆网配置一样会造成rx sequence错误。
基础测试不要一上来就跑最高采样率,先用保守参数跑通流程,再逐步加压观察边界,这样更容易把问题定位到具体环节上。
4. 用状态传感器和错误计数判断板卡的真实健康状态
4.1 从probe和传感器里读出温度、电源与锁定状态
回环测试通过后,不要急着认为板卡完全健康。我还会花时间去看板卡内部传感器数据,这些数据平时不被注意,但它们直接反映硬件工作状态。
获取传感器列表可以用一条简单的Python循环:
for name in b210.get_mboard_sensor_names(): sensor = b210.get_mboard_sensor(name) print(name, sensor.to_dict())N310和B210可用的传感器不完全一样,常见的几类包括:
ref_locked:参考时钟是否锁定。无论用内部还是外部参考,这个值都应该是true。temp:板卡温度。B210是被动散热,长时间大功率发射时温度上升会比较明显;N310有风扇,但仍需关注高温告警。power相关的传感器:N310这类系统可能会暴露多个电源域的状态,任何一路异常都值得重视。- GPS相关的传感器:当N310启用GPSDO时,可以查看GPS锁定状态和GPS时间。
我的判断标准是:回环能通,只能说明信号路径通畅;温度异常、电源告警、时钟失锁这些都不一定会立刻让回环失败,但会埋下后续测试数据不可信的隐患。所以基础测试中我会连续监测这些传感器,至少跑15到30分钟,看关键指标是不是平稳。
4.2 流式传输时的错误码才是真正的隐患
传感器看完了,更关键还要看传输层的错误计数。UHD在接收数据时返回的metadata里携带error_code,常见的有:
ERROR_CODE_OVERFLOW:主机的接收数据没有及时取走,板卡侧数据溢出。这个在B210上见得多一些。ERROR_CODE_TIMEOUT:流超时,通常和网络配置或系统调度有关。ERROR_CODE_SEQUENCE:数据包的序列号不连续,说明中间发生了丢包,N310测试中一旦网卡队列丢包就会看到这一类错误。
Python里这样检查:
metadata = uhd.types.RXMetadata() rx_streamer.recv(rx_buffer, metadata) if metadata.error_code != uhd.types.RXMetadataErrorCode.NONE: print("收到错误码:", metadata.error_code)真正健康的板卡连续跑一段时间后,错误码应该一直保持NONE。我自己测试时习惯把错误计数累加起来,放到每条测试记录的末尾。如果发现overflows频繁,先看主机的USB控制器或者网卡是否正常工作,再看采样率和缓存参数是否激进,最后才怀疑板卡本身。
5. 把基础测试沉淀成可复用脚本:频点扫描与日志记录
5.1 构建一个简单的频点自动扫描流程
单次回环通过只是开始,实际验收板卡时我会在不同频点、不同增益下做批量扫描,确保板子在整个可用频带内都工作一致。下面是一个精简的扫描框架:
import uhd import numpy as np import time import json FREQ_LIST = [700e6, 1.8e9, 2.4e9, 3.5e9, 5.8e9] RATE = 25e6 DURATION = 0.1 usrp = uhd.usrp.MultiUSRP("type=b200") usrp.set_tx_rate(RATE) usrp.set_rx_rate(RATE) results = [] for freq in FREQ_LIST: usrp.set_tx_freq(freq) usrp.set_rx_freq(freq) time.sleep(0.5) # 可以在这里加一个本振锁定检测 try: lo_lock = usrp.get_rx_sensor("lo_locked").to_bool() if not lo_lock: results.append({"freq": freq, "status": "lo_unlock"}) continue except RuntimeError: pass # 发送并接收回环信号 # ...(省略采样和FFT判断的具体代码) results.append({"freq": freq, "status": "pass"}) with open("scan_result.json", "w") as f: json.dump(results, f, indent=2)这段代码里的两个细节值得注意。
第一,切换频率后要sleep一段时间再继续操作,给本振留出锁定时间。虽然UHD内部会在设置频率时同步等待锁相环完成,但不同频点间快速切换时我仍然建议至少留出几百毫秒,实测中这样可以显著减少偶发的lo_unlock误报。
第二,get_rx_sensor("lo_locked")这个传感器在某些型号或者某些UHD版本上不一定存在,所以我在外面包了try/except,避免因为某个版本缺传感器导致整个扫描流程崩掉。写测试脚本时一定要对硬件能力差异有预期,UHD跨版本的行为并不总是完全一致。
5.2 我在扫测过程中踩过的几个实用教训
第一,B210尽量别用机箱前置USB口做长时长测试。我遇到过前置USB口在测试进行到一半时设备直接掉线的情况,换到主板后置USB 3.0口之后,长时间测试稳定了很多。原因大多是前置面板的供电和信号质量都不如主板原生接口。
第二,N310的万兆网链路不要随便过交换机。最稳妥的是用一块支持SFP+的网卡直连板卡,避免交换机缓冲或者VLAN配置问题干扰测试。如果环境必须经过交换机,务必先跑长时长回环确认链路稳定,再开始正式测试。
第三,记录文件名一定要包含设备序列号和日期。多块板卡并行测试时,序列号是唯一可靠的标识,光靠备注很容易混。我的命名习惯是serial_date_freq_result.csv,路径固定,日志里同时写入UHD版本和固件版本,方便事后回溯问题。
第四,射频线缆回环测试里测到的功率带着线缆的插损,不能直接当绝对功率来验收。如果要对发射功率做严格验收,需要先校准线损,或者直接使用经过校准的频谱仪来测。基础测试阶段,我们更关心的是不同频点之间信号的相对一致性,而不是绝对功率值。
第五,别忽略温度对测试结果的影响。B210长时间满功率发射后温度升高,增益特性和本振相位噪声都会出现细微变化。批量扫描频点时,我一般每次扫完一轮让板卡自然冷却一段时间再继续,或者至少记录每个频点测试时的板卡温度,方便后续排查是否有热漂移。
写在最后:基础测试的价值在于建立那份“确定感”
做了这么多次基础测试,我最大的体会是:这些看似简单甚至枯燥的步骤,真正价值是让每一块板子在进入任何复杂应用之前,都有一份可对比的基线数据。默认状态下能不能正常枚举、回环峰值是否落在期望位置、传感器温度和错误计数是不是正常区间,这些数据也许不会写进最终论文或者产品手册,但它能让你在后续出现异常时迅速分清楚是板卡本身的个体问题,还是应用导致的边界问题。
我现在每拿到一张新板卡,都会固定跑一遍这套流程,把结果保存下来。哪天哪块板子表现出异常,翻出当初的基线一对照,基本能判断是环境因素还是硬件老化。建议你也把流程固化下来,越基础的东西越值得反复验证。