在 OFDM 雷达实验中,发送与接收之间首先需要解决一个问题:接收缓冲区里的哪一个采样点,对应这一帧 OFDM 数据的开始? 如果帧起点选错,后续去循环前缀、FFT、信道估计和距离速度处理都会受到影响。
本文以 MATLAB 构造的三目标回波为输入,利用 Python 和 NI USRP B210 完成 OFDM 调制、射频自回环采集及解调。实验从"添加同步符号寻找帧首"逐步改为"利用硬件时间戳和已标定回环时延定位帧首",最后在不添加额外同步符号的情况下恢复感知信号,绘制距离-速度图,即 RD 图。
文章按照三个步骤展开:先解释时间戳机制,再用代码说明它如何完成收发调度与帧定位,最后串起回波构造、B210 自回环和 RD 处理,并推导距离、速度坐标的来源。
1. 什么是时间戳机制
1.1 硬件时间戳标记的是哪一种时间
这里的"时间戳"指 USRP 设备时钟上的时间 。UHD 用 TimeSpec 表示这个时间,它可以用于预约一次发送、指定接收开始时刻,也可以随接收元数据一起返回,说明数据中的第一个采样点对应什么时刻。
例如,程序取得当前设备时间后,可以请求设备在未来某一时刻开始接收,并在稍后的另一时刻开始发送。电脑负责提前提交命令和采样数据,设备依据自身时钟执行这些事件。
需要区分三种时间:
| 时间 | 含义 | 本实验中的用途 |
|---|---|---|
| USRP 硬件时间 | 设备计数器所表示的时间 | 预约 TX/RX、标记采样点、计算帧位置 |
| 主机程序执行时间 | Ubuntu 上代码实际运行、线程被调度的时刻 | 提前准备并提交数据,保证流传输及时 |
| 日期时间 | 例如 20260810_092002 |
给实验结果文件夹命名 |
输出中的 TX scheduled timestamp: 3.173210750 s 是设备时间轴上的时刻,并不表示当天的"3 秒钟",也不是 Unix 时间戳。文件夹名称里的年月日时分秒,则来自电脑的日期时间。
对于同一台 B210 的自回环,TX 与 RX 可以在同一个设备时间基准下描述。若扩展为多台 USRP,还需要处理设备之间的时钟和时间对齐,不能直接认为不同设备的数值时间天然一致。
1.2 从采样序号到绝对设备时间
设实际接收数据的第一个采样点时间戳为 t 0 t_0 t0,接收采样率为 F s F_s Fs。当采样连续且没有丢失时,接收缓冲区第 k k k 个采样点的设备时间为
t R X k = t 0 + k F s , k = 0 , 1 , ... . (1) t_{\mathrm{RX}}k=t_0+\frac{k}{F_s},\qquad k=0,1,\ldots . \tag{1} tRXk=t0+Fsk,k=0,1,....(1)
这样,一个普通的复数数组就有了对应的时间轴。知道某一事件的设备时间,就可以反过来求出它在数组中的采样序号。
本实验的 F s = 10 M S / s F_s=10\ \mathrm{MS/s} Fs=10 MS/s,每个采样点间隔为
T s a m p = 1 F s = 100 n s . (2) T_{\mathrm{samp}}=\frac{1}{F_s}=100\ \mathrm{ns}. \tag{2} Tsamp=Fs1=100 ns.(2)
这个间隔描述的是采样网格,并不意味着整个收发链路的时间误差只有 100 ns。命令时刻的量化、滤波器群时延、回环标定误差和分数采样时延仍然需要考虑。
1.3 时间戳为什么能用于帧定位
设发射帧首的预约时间为 t T X t_{\mathrm{TX}} tTX,从这个发射时间参考点到接收数据中对应帧首的有效回环时延为 τ l o o p \tau_{\mathrm{loop}} τloop。接收帧首到达时刻可写为
t a r r = t T X + τ l o o p . (3) t_{\mathrm{arr}}=t_{\mathrm{TX}}+\tau_{\mathrm{loop}}. \tag{3} tarr=tTX+τloop.(3)
联立式(1)和式(3),得到接收帧首的分数采样位置
k f r a c = ( t T X + τ l o o p − t 0 ) F s . (4) k_{\mathrm{frac}} =\left(t_{\mathrm{TX}}+\tau_{\mathrm{loop}}-t_0\right)F_s. \tag{4} kfrac=(tTX+τloop−t0)Fs.(4)
以整数采样点截取时,使用
k s t a r t = round ( t T X + τ l o o p − t 0 ) F s . (5) \boxed{ k_{\mathrm{start}} =\operatorname{round}\!\left \\left(t_{\\mathrm{TX}}+\\tau_{\\mathrm{loop}}-t_0\\right)F_s \\right }. \tag{5} kstart=round(tTX+τloop−t0)Fs.(5)
时间戳给出收发事件的时间关系,回环时延标定把这个关系转换成可用于 OFDM 解调的帧起点。 τ l o o p \tau_{\mathrm{loop}} τloop 包含当前收发配置下的有效硬件链路时延,不能只按射频线缆长度计算,也不能把另一种采样率下的标定值直接当作通用常数。
特别需要使用实际接收元数据中的 t 0 t_0 t0。程序请求的 RX 开始时间与首个实际采样时间可以存在差别,二者混用会直接造成帧位置偏差。
1.4 除了减少同步搜索,还有哪些作用
时间戳使一次实验具有清楚的收发时间基准,作用不止于寻找帧首。
| 作用 | 在本实验中的体现 |
|---|---|
| 预约收发事件 | RX 提前启动,TX 在指定设备时刻发射 |
| 统一波形时间轴 | TX 与 RX 波形能够按设备时间绘制和比较 |
| 定位接收帧 | 已知 t 0 t_0 t0、 t T X t_{\mathrm{TX}} tTX 和有效时延后,直接计算截取位置 |
| 检查实验状态 | 可核对实际 RX 起点、帧到达时间与预期是否一致 |
| 支持离线复现 | 保存时间戳、采样率和帧位置后,能够重新截取、分析原始数据 |
| 扩展重复帧实验 | 可以预约帧间隔,建立一致的慢时间基准 |
这些作用都建立在命令及时提交、TX 数据持续供应、RX 数据连续的基础上。TX 欠载 U 或 RX 溢出 O 会破坏波形连续性,时间戳无法补回丢失的数据。本文使用已成功完成采集的 10 MS/s 实验说明处理流程。
2. 用代码实现时间戳收发与 OFDM 帧定位
本节选取 ofdm_b210_timestamp_no_sync_10M.py 的关键逻辑展示。代码片段省略了设备初始化、异常处理和绘图部分,用于解释机制;完整程序中的 TX 与 RX 由线程并行执行。
2.1 帧结构:去掉的是额外同步符号
实验中,每个 OFDM 符号包含 1024 个有效采样点和 128 个循环前缀采样点,共 1152 点。一帧有效数据包含 512 个 OFDM 符号。
| 版本 | 发送帧结构 | 总采样点数 | 10 MS/s 下的发送时长 |
|---|---|---|---|
| 保留同步符号 | 2 个同步符号 + 512 个数据符号 | 592128 | 59.2128 ms |
| 无同步符号 | 512 个数据符号 | 589824 | 58.9824 ms |
无同步版本保留循环前缀,只移除了帧首额外添加的两个同步 OFDM 符号。它减少了 2304 个采样点,即 230.4 μs,约占原 514 符号帧的 0.389%。这个实验的重点是验证帧首可以由时间基准确定。
2.2 预约 RX 和 TX 时刻
为了在接收缓冲区中保留一段发射前的数据,先启动 RX,再启动 TX。预约关系为
t R X , r e q = t n o w + T m a r g i n , t T X = t R X , r e q + N l e a d F s . (6) t_{\mathrm{RX,req}}=t_{\mathrm{now}}+T_{\mathrm{margin}}, \qquad t_{\mathrm{TX}}=t_{\mathrm{RX,req}}+\frac{N_{\mathrm{lead}}}{F_s}. \tag{6} tRX,req=tnow+Tmargin,tTX=tRX,req+FsNlead.(6)
本实验设置 T m a r g i n = 2 s T_{\mathrm{margin}}=2\ \mathrm{s} Tmargin=2 s,为主机提交命令和数据留下准备时间; N l e a d = 327680 N_{\mathrm{lead}}=327680 Nlead=327680,对应 32.768 ms 的接收提前量。
python
# 以下变量和流对象已由完整程序初始化
sample_rate = 10e6
rx_lead_samples = 327680
schedule_margin = 2.0
now = usrp.get_time_now().get_real_secs()
rx_start = now + schedule_margin
tx_start = rx_start + rx_lead_samples / sample_rate
# 预约有限长度接收;stream_now=False 表示按设备时间执行
rx_command = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
rx_command.num_samps = capture_len
rx_command.stream_now = False
rx_command.time_spec = uhd.types.TimeSpec(rx_start)
rx_stream.issue_stream_cmd(rx_command)
# 保存 TX 的预约时间,随后写入 burst 首包元数据
tx_start_spec = uhd.types.TimeSpec(tx_start)
issue_stream_cmd() 的调用时刻是主机提交命令的时刻,time_spec 才是设备应该执行接收事件的时刻。预约的时间必须在提交时仍然足够靠后。
2.3 TX 时间戳写在 burst 首包上
发送样本通过 UHD 流接口分块提交。帧的第一包使用 has_time_spec=True,指定 burst 的开始时刻;后续包连续发送,不需要为每包重复预约起点。
python
# 摘录:此逻辑在 TX 线程中执行,RX 线程同时接收
sent_total = 0
first_packet = True
while sent_total < tx_samples.size:
chunk_end = min(sent_total + chunk_size, tx_samples.size)
packet = tx_samples[sent_total:chunk_end]
metadata = uhd.types.TXMetadata()
metadata.start_of_burst = first_packet
metadata.has_time_spec = first_packet
metadata.end_of_burst = False
if first_packet:
metadata.time_spec = tx_start_spec
sent = int(tx_stream.send(packet, metadata, 1.0))
if sent <= 0:
raise RuntimeError("未成功提交 TX 数据,应检查超时和预约时间")
# send 可能只接受部分数据,以实际返回数量推进
sent_total += sent
first_packet = False
# 完整程序还会发送 burst 结束标记,并检查全部样本是否提交成功
上面的代码突出时间戳与首包的关系。工程程序另有零返回重试、最后一个真实采样的 end_of_burst 标记以及收发完整性检查,不能仅凭发送计数就认定射频链路没有欠载。
主机端使用 fc32 表示复数浮点采样,UHD 传输端使用 sc16 表示复数整数采样。sc16 是传输数据格式,空口波形仍然由 OFDM 调制决定。
2.4 从 RX 元数据取得真实采样起点
收到第一批有效数据时,读取 RXMetadata.time_spec,记录实际首个采样的设备时间。
python
first_rx_timestamp = None
# 在完整接收循环内部执行
received = rx_stream.recv(chunk, rx_metadata, timeout)
if received > 0 and first_rx_timestamp is None:
first_rx_timestamp = rx_metadata.time_spec.get_real_secs()
# 接收完成后,为连续的 RX 数组建立时间轴
rx_time_axis = (
first_rx_timestamp + np.arange(rx_buffer.size) / sample_rate
)
后续把多次 recv() 得到的数据拼接成一个连续数组时,应确认中途没有溢出或缺样。一旦存在数据间断,不能继续用式(1)把所有拼接点当作等间隔连续采样。
2.5 用时间戳直接截取无同步 OFDM 帧
读取已标定的有效回环时延后,式(5)直接对应下面的代码。
python
# delay_seconds 来自当前采样率、当前硬件配置下的时延标定
frame_start_fractional = (
tx_start + delay_seconds - first_rx_timestamp
) * sample_rate
frame_start = int(np.rint(frame_start_fractional))
frame_samples = (1024 + 128) * 512
frame_end = frame_start + frame_samples
if frame_start < 0 or frame_end > rx_buffer.size:
raise RuntimeError("时间戳预测的帧超出接收缓冲区")
rx_frame = rx_buffer[frame_start:frame_end]
这里的 frame_start 是 Python 从 0 开始的采样索引。若把同一个索引用于 MATLAB 数组,需要加 1。没有同步符号时,帧首就是第一个数据 OFDM 符号的 CP 起点;若帧首仍有两个同步符号,数据起点还需加上 2 × 1152 = 2304 2\times1152=2304 2×1152=2304 点。
round() 引入的取整误差最多为半个采样间隔,但这只约束取整操作本身。存在分数采样时延时,仍可能留下子载波相位斜率,由后续硬件通道均衡吸收,或通过分数时延补偿进一步处理。
2.6 用一次真实输出核对定位过程
无同步符号的 10 MS/s 实验记录如下:
text
RX requested timestamp: 3.140442750 s
TX scheduled timestamp: 3.173210750 s
First RX sample timestamp: 3.140447575 s
Loaded no-sync calibration: loopback_delay_no_sync_10000000Hz.mat
No-sync frame start: 327727
No-sync match score: 0.8328420519828796
No-sync calibration used: False
TX sent: 589824 / 589824
RX samples: 1245184
RX metadata error count: 0
实际 RX 首样本比请求时刻晚了
3.140447575 − 3.140442750 = 4.825 μ s = 48.25 个采样间隔 . (7) 3.140447575-3.140442750 =4.825\ \mu\mathrm{s} =48.25\ \text{个采样间隔}. \tag{7} 3.140447575−3.140442750=4.825 μs=48.25 个采样间隔.(7)
结合帧首索引,当前缓存使用的有效回环时延可以反算为
τ l o o p = t 0 + k s t a r t F s − t T X = 3.140447575 + 327727 10 7 − 3.173210750 = 9.525 μ s . (8) \begin{aligned} \tau_{\mathrm{loop}} &=t_0+\frac{k_{\mathrm{start}}}{F_s}-t_{\mathrm{TX}}\\ &=3.140447575+\frac{327727}{10^7}-3.173210750\\ &=9.525\ \mu\mathrm{s}. \end{aligned} \tag{8} τloop=t0+Fskstart−tTX=3.140447575+107327727−3.173210750=9.525 μs.(8)
将它重新代入式(5):
k s t a r t = round ( 3.173210750 + 9.525 × 10 − 6 − 3.140447575 ) 10 7 = 327727. (9) k_{\mathrm{start}} =\operatorname{round}\!\left \\left(3.173210750+9.525\\times10\^{-6}-3.140447575\\right)10\^7 \\right =327727. \tag{9} kstart=round(3.173210750+9.525×10−6−3.140447575)107=327727.(9)
因此,rx_buffer[327727] 对应当前标定参考下的帧首,设备时间为 3.173220275 s。整个接收缓冲区包含发射前、有效帧和发射后数据,所以其长度 1245184 大于发射长度 589824。
不能把 327727 − 327680 = 47 327727-327680=47 327727−327680=47 点直接解释成全部回环时延,因为这个差值还包含实际 RX 首样本与请求时间之间的差别。上面的反算同时使用了 TX 时间戳和实际 RX 时间戳。
2.7 "无同步符号"与"时延标定"的关系
程序并没有添加额外的同步 OFDM 符号,但保留了低匹配分数时的自动标定机制:先在时间戳预测位置计算已知数据的匹配分数,如果过低,就使用前 4 个已知数据符号,在预测位置附近搜索一次,更新有效时延,并保存为 loopback_delay_no_sync_10000000Hz.mat。
标定公式仍然是
τ ^ l o o p = t 0 + k ^ s t a r t F s − t T X . (10) \hat\tau_{\mathrm{loop}} =t_0+\frac{\hat k_{\mathrm{start}}}{F_s}-t_{\mathrm{TX}}. \tag{10} τ^loop=t0+Fsk^start−tTX.(10)
后续在相同采样率和稳定硬件配置下加载这个时延,直接按时间戳定位。本次输出的 calibration_used: False 表明当前运行没有触发局部帧首搜索;match_score 是在已确定位置进行质量检查的结果。
因此,准确表述应是:不增加同步符号,并在已完成有效时延标定的条件下,用时间戳直接定位帧首。 如果更换采样率、射频带宽、硬件连接或其他影响时延的配置,应重新核对标定值。当前程序按采样率命名缓存,实际使用时还需要确认其他配置没有变化。
2.8 通过波形图观察时间戳的作用
下面分别给出保留同步符号和去掉同步符号的收发波形。图中的设备时间轴、TX 开始与结束位置、RX 首采样时间和接收帧位置,来自预约时间或接收元数据。

图 1:保留两个同步 OFDM 符号的时间戳版本,发送帧长度为 592128 点。

图 2:无同步符号版本,发送帧只包含 512 个数据 OFDM 符号,长度为 589824 点。
时间戳使图中的位置具有设备时间含义,而不再只是一个接收数组的下标。帧截取以后,还需要继续完成 OFDM 解调和频偏、通道校正,才能比较传输前后的感知数据。
3. 从预设回波到 B210 自回环 RD 图
3.1 按照重构流程组织实验
《OFDM 接收信号的重建》PPT 中的验证过程,可以整理为三个阶段:先直接对构造的频域回波做感知处理,再加入 OFDM 调制解调验证矩阵与采样序列转换,最后把理想传输链路换成 B210。
| 阶段 | 信号经过的处理 | 要确认的内容 |
|---|---|---|
| 频域软件验证 | 构造 rxSymbol → 除以 txSymbol → RD |
回波模型、相位符号、距离速度坐标正确 |
| OFDM 软件验证 | IFFT → 加 CP → 串行化 → 去 CP → FFT | 调制解调互逆、矩阵维度和排列顺序正确 |
| B210 自回环验证 | OFDM 调制 → 射频自回环 → 时间戳定位 → 解调与校正 → RD | 硬件传输后能否恢复预设目标的相位信息 |
完整处理顺序为:
text
目标距离、速度 + QPSK 发送符号 txSymbol
↓ 回波模型
rxSymbol
↓ 子载波维 IFFT、加 CP、按列串行化
复数 IQ 样本
↓ 定时 TX
B210 TX/RX → 衰减器及射频线缆 → B210 RX2
↓ 定时 RX、保存首样本时间戳
RX 接收缓冲区
↓ 时间戳 + 已标定有效回环时延
截取 OFDM 数据帧
↓ 去 CP、子载波维 FFT
CFO 补偿、硬件通道均衡、残余相位校正
↓ 除以原始 txSymbol
sensing_signal
↓ 1024 点距离 IFFT、512 点速度 FFT
RD 图
这里存在一个容易混淆的命名:rxSymbol 是软件预先构造的雷达回波符号矩阵,但在自回环实验中,它恰好是送给 B210 发射的内容。txSymbol 则是用于构造回波和后续消除通信调制的原始 QPSK 序列。
为保持推导清楚,本文记原始发送符号为 X n , m Xn,m Xn,m,构造的回波符号为 Y n , m Yn,m Yn,m,B210 采集并解调得到的符号为 Z n , m Zn,m Zn,m。
3.2 统一参数与矩阵维度
矩阵的行对应子载波,列对应 OFDM 符号:
X , Y , Z ∈ C 1024 × 512 , n = 0 , ... , 1023 , m = 0 , ... , 511. (11) X,Y,Z\in\mathbb{C}^{1024\times512}, \qquad n=0,\ldots,1023,\quad m=0,\ldots,511. \tag{11} X,Y,Z∈C1024×512,n=0,...,1023,m=0,...,511.(11)
原始 MATLAB 回波模型使用以下参数:
| 参数 | 符号 | 数值 |
|---|---|---|
| 光速 | c 0 c_0 c0 | 3 × 10 8 m / s 3\times10^8\ \mathrm{m/s} 3×108 m/s |
| 载频 | f c f_c fc | 2 G H z 2\ \mathrm{GHz} 2 GHz |
| 波长 | λ = c 0 / f c \lambda=c_0/f_c λ=c0/fc | 0.15 m 0.15\ \mathrm{m} 0.15 m |
| 子载波数 | N N N | 1024 |
| OFDM 数据符号数 | M M M | 512 |
| CP 长度 | N C P N_{\mathrm{CP}} NCP | 128 点 |
| 模型带宽 | B m o d e l B_{\mathrm{model}} Bmodel | 50 M H z 50\ \mathrm{MHz} 50 MHz |
| 模型子载波间隔 | Δ f = B m o d e l / N \Delta f=B_{\mathrm{model}}/N Δf=Bmodel/N | 48.828125 k H z 48.828125\ \mathrm{kHz} 48.828125 kHz |
| 模型有效符号时长 | T u = 1 / Δ f T_u=1/\Delta f Tu=1/Δf | 20.48 μ s 20.48\ \mu\mathrm{s} 20.48 μs |
| 模型 CP 时长 | T C P T_{\mathrm{CP}} TCP | 2.56 μ s 2.56\ \mu\mathrm{s} 2.56 μs |
| 模型总符号时长 | T s = T u + T C P T_s=T_u+T_{\mathrm{CP}} Ts=Tu+TCP | 23.04 μ s 23.04\ \mu\mathrm{s} 23.04 μs |
| 模型相干处理时长 | M T s MT_s MTs | 11.79648 m s 11.79648\ \mathrm{ms} 11.79648 ms |
| 硬件播放、采集速率 | F s , h w F_{s,\mathrm{hw}} Fs,hw | 10 M S / s 10\ \mathrm{MS/s} 10 MS/s |
目标设置为:
| 目标 | 距离 R p R_p Rp | 径向速度 v p v_p vp | 相对幅度 |
|---|---|---|---|
| 目标 1 | 30 m | 0 m/s | 1 |
| 目标 2 | 60 m | 20 m/s | 1 |
| 目标 3 | 100 m | -30 m/s | 1 |
速度正负由本文回波模型中的多普勒相位约定确定。
原始回波模型的 50 MHz 与硬件播放速率 10 MS/s 是两个不同参数。 当前实验把已生成的 1024 × 512 1024\times512 1024×512 矩阵作为待传输的数据,以 10 MS/s 播放。硬件每个 OFDM 符号实际持续
T s , h w = 1024 + 128 10 7 = 115.2 μ s , (12) T_{s,\mathrm{hw}}=\frac{1024+128}{10^7}=115.2\ \mu\mathrm{s}, \tag{12} Ts,hw=1071024+128=115.2 μs,(12)
用于 CFO 估计的时间应采用这个实际符号时长。比较传输前后冻结的回波矩阵时,RD 坐标仍采用构造该矩阵时的 Δ f \Delta f Δf 和 T s T_s Ts,才能比较原来的三个预设目标。
如果把该波形重新解释为真实的 10 MHz 雷达波形,则应同时改变距离轴和速度轴: Δ f = 10 7 / 1024 \Delta f=10^7/1024 Δf=107/1024, T s = 115.2 μ s T_s=115.2\ \mu\mathrm{s} Ts=115.2 μs。在相同矩阵相位下,距离坐标变为原来的 5 倍,速度坐标变为原来的 1/5。不能据此认为实际 10 MHz 射频信号获得了 50 MHz 波形的物理距离分辨率。
3.3 根据目标距离和速度构造回波
对于单基地雷达,第 p p p 个目标的双程传播时延和多普勒频率为
τ p = 2 R p c 0 , f D , p = 2 v p f c c 0 . (13) \tau_p=\frac{2R_p}{c_0}, \qquad f_{D,p}=\frac{2v_pf_c}{c_0}. \tag{13} τp=c02Rp,fD,p=c02vpfc.(13)
复基带时域回波可以写成
r ( t ) = ∑ p = 1 P α p s ( t − τ p ) e j 2 π f D , p t + w ( t ) . (14) r(t)=\sum_{p=1}^{P}\alpha_p\,s(t-\tau_p) e^{j2\pi f_{D,p}t}+w(t). \tag{14} r(t)=p=1∑Pαps(t−τp)ej2πfD,pt+w(t).(14)
在目标时延位于 CP 保护范围内、一个符号内的多普勒影响较小、忽略显著子载波间干扰的条件下,去 CP 并进行 OFDM 解调,可以得到频域模型
Y n , m = X n , m ∑ p = 1 P A p e − j 2 π n Δ f τ p e j 2 π m T s f D , p + W n , m . (15) \boxed{ Yn,m=Xn,m\sum_{p=1}^{P}A_p e^{-j2\pi n\Delta f\tau_p} e^{j2\pi mT_sf_{D,p}}+Wn,m }. \tag{15} Yn,m=Xn,mp=1∑PApe−j2πnΔfτpej2πmTsfD,p+Wn,m.(15)
这对应参考论文第 3 章 3.1.2 中式(3.7)至式(3.9)的建模思路:把延迟和多普勒转换为频域符号矩阵里的两种相位变化。
距离项 e − j 2 π n Δ f τ p e^{-j2\pi n\Delta f\tau_p} e−j2πnΔfτp 沿子载波维产生相位斜率;速度项 e j 2 π m T s f D , p e^{j2\pi mT_sf_{D,p}} ej2πmTsfD,p 沿 OFDM 符号维产生相位斜率。固定的传播相位、散射幅度和归一化常数均可并入复幅度 A p A_p Ap。
原 MATLAB 程序采用 A p = a p exp ( j 2 π f c τ p ) / N A_p=a_p\exp(j2\pi f_c\tau_p)/\sqrt{N} Ap=apexp(j2πfcτp)/N 。这个不随 n , m n,m n,m 改变的常数相位,不改变距离与速度的峰值位置。
以下是对应的 MATLAB 构造代码,保留了原程序的参数与幅度约定:
matlab
% 行是子载波,列是 OFDM 符号
c0 = 3e8;
fc = 2e9;
N = 1024;
M = 512;
cpLen = 128;
modelRate = 50e6;
df = modelRate / N;
Ts = (N + cpLen) / modelRate;
R = [30, 60, 100];
v = [0, 20, -30];
amp = [1, 1, 1];
n = (0:N-1).';
m = 0:M-1;
rng(20260805);
bitI = randi([0, 1], N, M);
bitQ = randi([0, 1], N, M);
txSymbol = ((2*bitI-1) + 1j*(2*bitQ-1)) / sqrt(2);
rxSymbol = complex(zeros(N, M));
for p = 1:numel(R)
tau = 2 * R(p) / c0;
fd = 2 * v(p) * fc / c0;
A = amp(p) / sqrt(N) * exp(1j * 2*pi*fc*tau);
rangePhase = exp(-1j * 2*pi*n*df*tau);
speedPhase = exp(1j * 2*pi*m*Ts*fd);
rxSymbol = rxSymbol + A * txSymbol .* ...
(rangePhase * speedPhase);
end
% 给构造的回波加入 30 dB 信噪比的复高斯噪声
signalPower = mean(abs(rxSymbol(:)).^2);
noisePower = signalPower / 10^(30/10);
noise = sqrt(noisePower/2) * (randn(N,M) + 1j*randn(N,M));
rxSymbol = rxSymbol + noise;
此时三个目标的信息已经编码在 rxSymbol 中。硬件实验要检验的是,这些相位信息能否经过 OFDM 调制、真实射频链路和解调后继续被恢复。
3.4 OFDM 调制与解调:保持维度和顺序一致
对第 m m m 个符号,OFDM 调制为
x m q = 1 N ∑ n = 0 N − 1 Y n , m e j 2 π n q / N , q = 0 , ... , N − 1. (16) x_mq=\frac{1}{N}\sum_{n=0}^{N-1}Yn,m e^{j2\pi nq/N},\qquad q=0,\ldots,N-1. \tag{16} xmq=N1n=0∑N−1Yn,mej2πnq/N,q=0,...,N−1.(16)
把最后 128 个有效采样复制到符号前面,形成 CP,然后按符号顺序串行发送。
python
N = 1024
CP = 128
M = 512
# 每一列独立进行 OFDM 调制
time_symbols = np.fft.ifft(rx_symbol, n=N, axis=0)
with_cp = np.concatenate((time_symbols[-CP:, :], time_symbols), axis=0)
# 列优先:先发送第 0 列的完整符号,再发送第 1 列
tx_scale = float(np.max(np.abs(with_cp)))
tx_samples = (with_cp.reshape(-1, order="F") / tx_scale).astype(np.complex64)
# rx_frame 已通过时间戳定位,包含 512 个完整数据符号
rx_symbols_time = rx_frame.reshape((N + CP, M), order="F")
rx_symbols_frequency = np.fft.fft(rx_symbols_time[CP:, :], n=N, axis=0)
order="F" 与 MATLAB 的列优先排列一致。若发送端按列串行化而接收端按行恢复,即使样本一个都没丢,解调结果也会错误。
发送前的幅度归一化防止 IQ 波形超出接口范围;它引入的整体比例变化会进入后续估计的硬件通道。在纯软件理想链路中,如果不经过通道均衡,比较符号时需要把这个比例恢复。
还要区分本文中的两个 IFFT:OFDM 调制 IFFT 的输入是 rxSymbol,输出为可发送的时域 IQ;距离处理 IFFT 的输入是消除通信符号后的感知矩阵,输出为距离像。二者虽然都沿子载波维执行,但处理目的和所在阶段不同。
3.5 用 B210 替代理想传输链路
自回环的射频连接为:
text
B210 RF A:TX/RX → 衰减器、射频线缆 → 同通道 RX2

图 3:实验中的 B210 射频端口连接,选用 RF A 的 TX/RX 和 RX2。
在理想软件传输阶段,可以认为额外传输通道为 1;经过 B210 后,还会出现硬件频率响应、整体增益与相位、残余频偏和噪声。帧正确定位、残余 CFO 足够小时,可近似写成
Z n , m ≈ G n Y n , m e j θ m + V n , m , (17) Zn,m\approx GnYn,me^{j\theta_m}+Vn,m, \tag{17} Zn,m≈GnYn,mejθm+Vn,m,(17)
其中 G n Gn Gn 是硬件回环通道, θ m \theta_m θm 是每个符号的公共相位变化。较大的 CFO 还会产生 ICI,式(17)的简单乘法近似就不够准确。
时间戳解决帧在什么位置的问题,频偏与通道校正解决帧内数据被怎样改变的问题。 两者需要配合。
3.5.1 用已知回波符号估计频偏
本实验的发射内容 Y = rxSymbol Y=\texttt{rxSymbol} Y=rxSymbol 已知,可以先粗解调得到 Z Z Z,构造
Q n , m = Z n , m Y ∗ n , m ∣ Y n , m ∣ 2 + ε . (18) Qn,m=\frac{Zn,mY^*n,m}{|Yn,m|^2+\varepsilon}. \tag{18} Qn,m=∣Yn,m∣2+εZn,mY∗n,m.(18)
再对相邻 OFDM 符号形成相关量
C m = ∑ n Q n , m + 1 Q ∗ n , m . (19) C_m=\sum_n Qn,m+1Q^*n,m. \tag{19} Cm=n∑Qn,m+1Q∗n,m.(19)
若主要相位变化来自残余 CFO,相邻符号的相位增量近似为 2 π f ϵ T s , h w 2\pi f_\epsilon T_{s,\mathrm{hw}} 2πfϵTs,hw。原程序对这些相位作圆周平均:
f ^ ϵ = arg ( ∑ m = 0 M − 2 e j arg C m ) 2 π T s , h w . (20) \hat f_\epsilon= \frac{\arg\!\left(\sum_{m=0}^{M-2}e^{j\arg C_m}\right)} {2\pi T_{s,\mathrm{hw}}}. \tag{20} f^ϵ=2πTs,hwarg(∑m=0M−2ejargCm).(20)
随后在时域采样上补偿,再重新进行 OFDM 解调:
r ~ k = r k exp ( − j 2 π f ^ ϵ k F s , h w ) . (21) \tilde rk=rk\exp\!\left(-j2\pi\hat f_\epsilon\frac{k}{F_{s,\mathrm{hw}}}\right). \tag{21} r~k=rkexp(−j2πf^ϵFs,hwk).(21)
这里使用硬件实际的 T s , h w = 115.2 μ s T_{s,\mathrm{hw}}=115.2\ \mu\mathrm{s} Ts,hw=115.2 μs。相邻相位法存在相位模糊,且依赖近似对角通道;已知矩阵中的低幅度元素也会放大比值噪声,因此它适合当前的小频偏、已知信号验证场景。
3.5.2 均衡硬件通道,并校正残余相位
频偏补偿后,原程序沿 OFDM 符号维平均 Q Q Q,估计各子载波的硬件通道:
G ^ n = 1 M ∑ m = 0 M − 1 Q n , m . (22) \hat Gn=\frac{1}{M}\sum_{m=0}^{M-1}Qn,m. \tag{22} G^n=M1m=0∑M−1Qn,m.(22)
为避免在深衰落子载波上直接除以很小的数,程序设置幅度下限
G f l o o r = max ( 0.05 median n ∣ G ^ n ∣ , 10 − 4 ) , (23) G_{\mathrm{floor}} =\max\!\left(0.05\operatorname{median}_n|\hat Gn|,\,10^{-4}\right), \tag{23} Gfloor=max(0.05mediann∣G^n∣,10−4),(23)
保留估计相位,并对小于下限的幅度进行限制,形成 G ^ s a f e n \hat G_{\mathrm{safe}}n G^safen。均衡结果为
Z e q n , m = Z C F O n , m G ^ s a f e n . (24) Z_{\mathrm{eq}}n,m=\frac{Z_{\mathrm{CFO}}n,m}{\hat G_{\mathrm{safe}}n}. \tag{24} Zeqn,m=G^safenZCFOn,m.(24)
接着,利用已知回波符号计算每个符号的公共相位:
ϕ m = unwrap arg ( ∑ n Z e q \[ n , m Y ∗ n , m ) ] . (25) \phi_m=\operatorname{unwrap}\!\left \\arg\\!\\left(\\sum_n Z_{\\mathrm{eq}}\[n,mY^*n,m\right) \right]. \tag{25} ϕm=unwraparg(n∑Zeq\[n,mY∗n,m)].(25)
原程序对 ϕ m \phi_m ϕm 作加权线性拟合 ϕ ^ m = a m + b \hat\phi_m=am+b ϕ^m=am+b,并校正为
Z c o r r n , m = Z e q n , m e − j ϕ ^ m . (26) Z_{\mathrm{corr}}n,m =Z_{\mathrm{eq}}n,me^{-j\hat\phi_m}. \tag{26} Zcorrn,m=Zeqn,me−jϕ^m.(26)
这一步针对相位常数和残余线性相位漂移,并不意味着所有随机相位噪声都被消除。
当前实验使用完整已知的 rxSymbol 做校正,因此可以将预先编码的目标相位与额外硬件相位区分开。实际未知目标感知不能假定目标回波矩阵已知,需要独立参考链路、导频或其他估计方法。若直接把真实感知信号的慢时间相位当成误差全部去掉,也可能把目标多普勒一起消除。
3.6 消除原始发送序列,得到感知信号
硬件校正后,用原始 QPSK 符号 X X X 去掉通信调制:
S n , m = Z c o r r n , m X ∗ n , m ∣ X n , m ∣ 2 + ε . (27) Sn,m =\frac{Z_{\mathrm{corr}}n,mX^*n,m}{|Xn,m|^2+\varepsilon}. \tag{27} Sn,m=∣Xn,m∣2+εZcorrn,mX∗n,m.(27)
对于当前所有子载波上非零、单位模的 QPSK,式(27)近似等于直接相除。理想情况下,得到
S n , m ≈ ∑ p = 1 P A p e − j 2 π n Δ f τ p e j 2 π m T s f D , p + W ~ n , m . (28) Sn,m\approx\sum_{p=1}^{P}A_p e^{-j2\pi n\Delta f\tau_p} e^{j2\pi mT_sf_{D,p}}+\widetilde Wn,m. \tag{28} Sn,m≈p=1∑PApe−j2πnΔfτpej2πmTsfD,p+W n,m.(28)
对应 Python 代码为:
python
# rx_equalized 在原程序中已经包含残余线性公共相位校正
sensing_signal = (
rx_equalized * np.conj(tx_symbol)
/ (np.abs(tx_symbol) ** 2 + 1e-12)
)
这里的两个参考用途不同:用 rxSymbol 校正额外硬件通道,用 txSymbol 消除原始通信符号。若直接把硬件输出除以已知 rxSymbol 当作最终感知信号,得到的主要是额外硬件通道,预设目标的信息反而会被消掉。
残余频偏会使式(28)多出慢时间相位 e j 2 π f ϵ m T s , h w e^{j2\pi f_\epsilon mT_{s,\mathrm{hw}}} ej2πfϵmTs,hw。用原模型 T s T_s Ts 标定速度时,其引起的速度偏移近似为
Δ v = c 0 2 f c f ϵ T s , h w T s . (29) \Delta v =\frac{c_0}{2f_c}f_\epsilon\frac{T_{s,\mathrm{hw}}}{T_s}. \tag{29} Δv=2fcc0fϵTsTs,hw.(29)
只有硬件符号时长与雷达模型符号时长一致时,这才简化为常见的 Δ v = c 0 f ϵ / ( 2 f c ) \Delta v=c_0f_\epsilon/(2f_c) Δv=c0fϵ/(2fc)。这也解释了为什么帧位置正确后,仍然需要 CFO 与残余相位校正。
3.7 距离 IFFT 与速度 FFT 的推导
对感知矩阵先沿子载波维做 L = 1024 L=1024 L=1024 点 IFFT:
P ℓ , m = 1 L ∑ n = 0 N − 1 S n , m e j 2 π n ℓ / L , ℓ = 0 , ... , L − 1. (30) P\\ell,m=\frac{1}{L}\sum_{n=0}^{N-1}Sn,m e^{j2\pi n\ell/L},\qquad \ell=0,\ldots,L-1. \tag{30} Pℓ,m=L1n=0∑N−1Sn,mej2πnℓ/L,ℓ=0,...,L−1.(30)
再沿 OFDM 符号维做 M = 512 M=512 M=512 点 FFT:
D ℓ , q = ∑ m = 0 M − 1 P ℓ , m e − j 2 π m q / M . (31) D\\ell,q=\sum_{m=0}^{M-1}P\\ell,m e^{-j2\pi mq/M}. \tag{31} Dℓ,q=m=0∑M−1Pℓ,me−j2πmq/M.(31)
为看清峰值位置,先考虑一个无噪声目标。将式(28)代入,可将二维变换写成两个求和项的乘积:
D ℓ , q = A L ∑ n = 0 N − 1 e j 2 π n ( ℓ / L − Δ f τ ) ⋅ ∑ m = 0 M − 1 e j 2 π m ( T s f D − q / M ) . (32) \begin{aligned} D\\ell,q =\frac{A}{L} &\left\\sum_{n=0}\^{N-1} e\^{j2\\pi n(\\ell/L-\\Delta f\\tau)}\\right\\ \cdot &\left\\sum_{m=0}\^{M-1} e\^{j2\\pi m(T_sf_D-q/M)}\\right. \end{aligned} \tag{32} Dℓ,q=LA⋅n=0∑N−1ej2πn(ℓ/L−Δfτ)m=0∑M−1ej2πm(TsfD−q/M).(32)
距离求和项在 ℓ / L ≈ Δ f τ \ell/L\approx\Delta f\tau ℓ/L≈Δfτ 时最强,慢时间求和项在 q / M ≈ T s f D q/M\approx T_sf_D q/M≈TsfD 时最强。因此
τ ℓ = ℓ L Δ f , R ℓ = c 0 ℓ 2 L Δ f . (33) \tau_\ell=\frac{\ell}{L\Delta f}, \qquad \boxed{R_\ell=\frac{c_0\ell}{2L\Delta f}}. \tag{33} τℓ=LΔfℓ,Rℓ=2LΔfc0ℓ.(33)
速度维在 fftshift 后使用有符号频点 d = − M / 2 , ... , M / 2 − 1 d=-M/2,\ldots,M/2-1 d=−M/2,...,M/2−1,得到
f D , d = d M T s , v d = c 0 d 2 f c M T s . (34) f_{D,d}=\frac{d}{MT_s}, \qquad \boxed{v_d=\frac{c_0d}{2f_cMT_s}}. \tag{34} fD,d=MTsd,vd=2fcMTsc0d.(34)
在当前 L = N L=N L=N 且不加窗的设置下,距离栅格间隔为
Δ R = c 0 2 N Δ f = c 0 2 B m o d e l = 3 m , (35) \Delta R=\frac{c_0}{2N\Delta f} =\frac{c_0}{2B_{\mathrm{model}}}=3\ \mathrm{m}, \tag{35} ΔR=2NΔfc0=2Bmodelc0=3 m,(35)
速度栅格间隔为
Δ v = c 0 2 f c M T s ≈ 6.357829 m / s . (36) \Delta v=\frac{c_0}{2f_cMT_s} \approx6.357829\ \mathrm{m/s}. \tag{36} Δv=2fcMTsc0≈6.357829 m/s.(36)
增加零填充 FFT 点数可以让显示栅格更密,但不会凭空增加实际带宽或相干观测时长,也不会等价地提高物理分辨率。
距离相位的周期对应 c 0 / ( 2 Δ f ) = 3072 m c_0/(2\Delta f)=3072\ \mathrm{m} c0/(2Δf)=3072 m;但式(15)的普通 CP 对角模型还要求目标时延在 CP 保护范围内。当前 T C P = 2.56 μ s T_{\mathrm{CP}}=2.56\ \mu\mathrm{s} TCP=2.56 μs 对应约 384 m,不能把整个 3072 m 周期都当作这个简单模型的有效测距范围。三个预设目标都满足 CP 条件。
3.8 与推导一致的 MATLAB RD 代码
下面的程序从保存的 no_sync_sensing_signal.mat 读取感知矩阵,并按原始回波模型的参数生成 RD 图。
matlab
clear;
clc;
% 选择待分析的感知矩阵,原程序保存了 senseSignal 与 sensing_signal 两个别名
[fileName, filePath] = uigetfile('*.mat', '选择感知信号 MAT 文件');
if isequal(fileName, 0)
return;
end
data = load(fullfile(filePath, fileName));
if isfield(data, 'senseSignal')
S = data.senseSignal;
elseif isfield(data, 'sensing_signal')
S = data.sensing_signal;
else
error('文件中没有 senseSignal 或 sensing_signal 变量');
end
assert(isequal(size(S), [1024, 512]), '感知矩阵应为 1024×512');
assert(all(isfinite(S(:))), '感知矩阵包含非有限值');
c0 = 3e8;
fc = 2e9;
N = 1024;
M = 512;
cpLen = 128;
% 用构造 rxSymbol 时的模型参数比较传输前后的 RD
% 这里的 50 MHz 是原始模型参数,不是 B210 本次的 10 MS/s 播放速率
modelRate = 50e6;
df = modelRate / N;
Ts = (N + cpLen) / modelRate;
rangeProfile = ifft(S, N, 1); % 行:子载波 → 距离
rdMap = fftshift(fft(rangeProfile, M, 2), 2); % 列:符号 → 多普勒
peak = max(abs(rdMap(:)));
rdDb = 20 * log10(abs(rdMap) / max(peak, eps) + eps);
rangeAxis = (0:N-1) * c0 / (2*N*df);
speedAxis = (-M/2:M/2-1) * c0 / (2*fc*M*Ts);
rangeMask = rangeAxis <= 150;
speedMask = abs(speedAxis) <= 60;
figure('Color', 'w');
imagesc(speedAxis(speedMask), rangeAxis(rangeMask), ...
rdDb(rangeMask, speedMask));
axis xy;
xlabel('速度 / m/s');
ylabel('距离 / m');
title(['OFDM RD 图:', fileName], 'Interpreter', 'none');
colormap turbo;
colorbar;
caxis([-40, 0]);
本例为便于对照,未加距离窗和速度窗,处理方式与第一版软件仿真一致。20*log10 作用于归一化幅度,等价于对相应归一化功率使用 10*log10。
3.9 软件基准与无同步硬件结果的比较
原始软件感知矩阵的 RD 图如下:

图 4:由原始 senseSignal 得到的参考 RD 图。
经过无同步符号 B210 自回环、时间戳定位和校正后,得到:

图 5:由硬件回环恢复的感知矩阵得到的 RD 图,采用与图 4 相同的模型坐标。
读取工程中保存的两份 RD 结果矩阵,可核对三个目标的峰值:
| 目标 | 预设距离、速度 | 软件基准峰值 | 无同步硬件峰值 |
|---|---|---|---|
| 目标 1 | 30 m,0 m/s | 30 m,0 m/s | 30 m,0 m/s |
| 目标 2 | 60 m,20 m/s | 60 m,19.073486 m/s | 60 m,19.073486 m/s |
| 目标 3 | 100 m,-30 m/s | 99 m,-31.789144 m/s | 99 m,-31.789144 m/s |
三个峰值落在相同的距离-速度栅格上,说明预先编码的距离和多普勒信息经过这条硬件调制解调链路后得到了保留。这不等于传输前后的矩阵逐元素完全一致。
目标 3 的峰值显示为 99 m 而不是 100 m,是因为距离栅格为 3 m, 100 / 3 = 33.333 ... 100/3=33.333\ldots 100/3=33.333...,最近的第 33 个距离频点对应 99 m。同理,20 m/s 最近的速度频点为 3 Δ v = 19.073486 m / s 3\Delta v=19.073486\ \mathrm{m/s} 3Δv=19.073486 m/s,-30 m/s 最近的速度频点为 − 5 Δ v = − 31.789144 m / s -5\Delta v=-31.789144\ \mathrm{m/s} −5Δv=−31.789144 m/s。
目标 2、3 周围的条带也能由式(32)解释。有限长度的指数求和满足
∑ k = 0 K − 1 e j 2 π k η = e j π ( K − 1 ) η sin ( π K η ) sin ( π η ) . (37) \sum_{k=0}^{K-1}e^{j2\pi k\eta} =e^{j\pi(K-1)\eta}\frac{\sin(\pi K\eta)}{\sin(\pi\eta)}. \tag{37} k=0∑K−1ej2πkη=ejπ(K−1)ηsin(πη)sin(πKη).(37)
当目标恰好落在整数 FFT 栅格上,能量较集中;偏离栅格时,会产生主瓣与旁瓣。目标 1 的距离和速度均落在栅格上,所以呈现为更集中的峰;目标 2 的速度不在栅格上,目标 3 的距离和速度均不在栅格上,因此旁瓣更明显。
这些条带在软件基准中已经存在,不能全部归因于硬件杂波或缺少 MTI。加窗可以降低旁瓣,但会加宽主瓣;MTI 则主要针对静止或低速背景,在本例中还可能抑制需要保留的 30 m 静止目标。
本次终端还输出了:
text
Estimated CFO from known data: 7.685727486638082 Hz
B210 channel median magnitude: 1.4617857933044434
B210 channel equalizer floor: 0.07308928966522217
Residual CFO from CPE: -7.706771553682997 Hz
NMSE raw: 2.0302579402923584
NMSE equalized: 0.5306269526481628
CFO 和 CPE 的数值分别来自两次估计阶段,正负关系取决于该阶段剩余的相位斜率;最终比较应结合校正后的矩阵误差及 RD 峰值。通道幅度下限约为中位数幅度的 5%,与式(23)一致。
需要说明原程序中 NMSE 的实际定义。代码计算的是
E = ∥ Z c o r r − Y ∥ F ∥ Y ∥ F , (38) E=\frac{\lVert Z_{\mathrm{corr}}-Y\rVert_F}{\lVert Y\rVert_F}, \tag{38} E=∥Y∥F∥Zcorr−Y∥F,(38)
它是相对 Frobenius 范数误差,没有平方。常规定义的归一化均方误差为 N M S E = E 2 \mathrm{NMSE}=E^2 NMSE=E2,因此这里的 0.530627 对应约 0.281565,不能直接当作噪声功率或据此唯一反推出噪声标准差。峰值位置一致与逐元素误差非零可以同时成立。
这条实验链路最终验证了:构造的三目标回波能够经过 OFDM 调制、B210 射频自回环、时间戳帧定位及解调校正,并在相同模型坐标下恢复相同的 RD 峰值栅格。三个距离和速度来自软件预设相位,射频线缆自回环验证的是硬件链路对这些信息的传输能力;面向真实空间目标时,还需要实际照射与接收回波,以及独立的硬件误差估计手段。
参考资料
- 硕士论文:《基于 USRP 的 OFDM 通感一体化系统设计与实现》,第 3 章 3.1.2,式(3.7)至式(3.9)的接收信号模型。
- UHD 官方文档:Device Synchronization,用于设备时间及定时收发接口的进一步阅读。