3. OFDM基带信号生成
水声OFDM发射端的基带信号生成,本质上是把一串频域上的QAM/PSK符号“搬”到时域波形上,再为它加上抵抗多途扩展的保护结构。这一节我们沿着数据流走一遍:从比特映射出的频域符号如何排进子载波、IFFT如何完成频域到时域的变换、循环前缀为什么能对抗水声信道的长时延扩展,以及最终一帧基带波形由哪些部分组成。理解这条链路,是后续加同步前导、上变频和信道仿真的前提。
一、子载波分配:哪些“格子”放数据,哪些留空
设系统采用 \(N\) 点IFFT,则可用子载波编号为 \(0 \sim N-1\)。实际水声OFDM不会把全部子载波都用于承载数据,通常要做三类划分:数据子载波用于承载调制符号;导频子载波按固定间隔插入已知符号,供接收端做信道估计与相位跟踪;保护子载波(空子载波)放在频带两端和直流附近,置零不用,用于抑制带外泄漏、避开换能器低频响应差和直流泄漏的区域。
以一个教学示例为例:取 \(N=1024\),采样率 \(f_s=48\,\text{kHz}\),子载波间隔 \(\Delta f = f_s/N \approx 46.9\,\text{Hz}\)。若有效带宽取 \(6\,\text{kHz}\),则占用约 \(128\) 个子载波。可规定:直流子载波 \(k=0\) 置零;低频端 \(k=1\sim4\) 与高频端对应位置作为保护;中间每隔 8 个子载波放 1 个导频,其余为数据子载波。分配结果可以用一个长度为 \(N\) 的频域向量 \(X[k]\) 表示,数据位置填调制符号,导频位置填已知符号(如BPSK的 \(+1\)),其余填 0。
需要特别注意共轭对称性:若希望IFFT输出为实信号,频域向量必须满足 \(X[N-k]=X^*[k]\)。水声OFDM常在中频或复基带处理,此时输出复信号即可,不必强制共轭对称;但若直接驱动D/A产生实通带信号,就必须构造厄米特对称,否则会出现镜像干扰。这一点在仿真中要作为明确的约定写进代码注释。
二、IFFT调制:从频域符号到时域样点
OFDM的调制就是一次 \(N\) 点IFFT。按常用定义(含 \(1/N\) 归一化):
\[ x[n]=\frac{1}{N}\sum_{k=0}^{N-1}X[k]\,e^{j2\pi kn/N},\quad n=0,1,\dots,N-1 \]
工程实现上直接调用FFT库的逆变换即可,但要注意库函数的归一化因子和正负号约定。MATLAB中 ifft(X, N) 自带 \(1/N\);Python的 numpy.fft.ifft 同样含 \(1/N\)。若自行用 fft 加共轭实现,需写成 conj(fft(conj(X)))/N,否则幅度会差一个 \(N\) 倍,导致后续功率归一化和峰均比统计全部失真。
IFFT之后得到的是一个OFDM符号的 \(N\) 个时域样点。此时应检查两个量:一是峰均功率比(PAPR),水声发射机功率受限,PAPR过高会迫使功放回退,降低效率;二是样点功率,便于后续统一归一化到D/A满量程的合适比例。教学示例中可对一帧内所有符号统计PAPR,观察其随子载波数和调制阶数的变化趋势。
三、循环前缀插入:把“尾巴”搬到“头上”
水声信道多途扩展可达数毫秒到数十毫秒。设最大时延扩展为 \(\tau_{\max}\),则循环前缀长度 \(N_{cp}\) 应满足 \(N_{cp}/f_s \ge \tau_{\max}\),并留一定余量。仍以上述参数为例,若 \(\tau_{\max}=10\,\text{ms}\),则 \(N_{cp}\ge 480\) 点,取 \(N_{cp}=512\) 点,对应约 \(10.7\,\text{ms}\) 保护间隔。
循环前缀的构造非常直接:取IFFT输出 \(x[n]\) 的末尾 \(N_{cp}\) 个样点,复制到该符号的最前面,形成长度 \(N+N_{cp}\) 的扩展符号。其物理意义是:只要信道时延扩展不超过CP长度,前一符号的拖尾只会落入后一符号的CP区间,而不会污染后一符号的有效数据段;同时,线性卷积在有效区间内等效为循环卷积,接收端一次FFT即可完成均衡,把频域信道变成一个子载波一个复系数。
代价也要说清楚:CP不携带新信息,带来 \(N_{cp}/(N+N_{cp})\) 的速率与功率开销。上例中开销约 \(33\%\),这是水声OFDM为对抗长多途必须付出的代价,也是水声系统带宽效率普遍低于射频OFDM的原因之一。
四、帧结构设计:把符号组织成可被接收端识别的整体
单个OFDM符号无法独立完成同步与信道估计,需要把若干符号组织成一帧。典型水声OFDM帧由三部分组成:前导(同步序列)、导频/数据符号区、可选的保护间隔。前导常用线性调频(LFM)或伪随机序列,用于帧检测、定时同步和多普勒粗估计;数据区由若干带CP的OFDM符号串联,其中按子载波插入的导频用于信道估计与残余多普勒的相位补偿。
设计帧结构时要回答几个具体问题:一帧包含多少个OFDM符号(影响时变信道下的信道估计更新率);导频在频域和时域上的密度(频域间隔应小于信道相干带宽,时域间隔应小于相干时间);前导长度与数据长度的比例。教学示例可设一帧 = 1个LFM前导 + 20个OFDM符号,每个符号 \(N+N_{cp}=1536\) 点,则一帧基带样点数约为 \(20\times1536\) 加上前导长度,可直接算出帧时长,用于和信道相干时间对比,判断导频密度是否合理。
五、仿真实现要点与结果判读
把上述四步写成代码时,建议按“频域向量—IFFT—加CP—串接成帧”的顺序组织,每一步都保留中间变量便于检查。下面给出核心片段(Python/NumPy,参数与上文示例一致):
import numpy as np
N, Ncp, fs = 1024, 512, 48000
n_sym = 20 # 一帧内OFDM符号数
data_idx = np.arange(5, 133) # 示例:有效子载波范围
pilot_idx = data_idx[::8] # 每8个取1个导频
data_idx = np.setdiff1d(data_idx, pilot_idx)
frame = []
for s in range(n_sym):
X = np.zeros(N, dtype=complex)
X[pilot_idx] = 1.0 + 0j # 导频:已知BPSK符号
X[data_idx] = (np.random.randint(0,4,len(data_idx)) # 示例QPSK
- 1.5) * 1.0
X[data_idx] = (np.sign(X[data_idx].real)
+ 1j*np.sign(X[data_idx].imag)) / np.sqrt(2)
x = np.fft.ifft(X) # 含1/N归一化
x_cp = np.concatenate([x[-Ncp:], x]) # 循环前缀
frame.append(x_cp)
frame = np.concatenate(frame)
print("单符号样点数:", N+Ncp, " 帧样点数:", frame.size)
print("CP时长(ms):", Ncp/fs*1e3)
运行后应核对三件事。第一,单符号样点数是否为 \(N+N_{cp}=1536\),帧样点数是否为 \(20\times1536\) 加上前导长度;第二,CP时长是否覆盖设定的最大多途扩展,示例中约 \(10.7\,\text{ms}\),若信道实测多途达 \(15\,\text{ms}\),就必须增大 \(N_{cp}\) 或降低符号率;第三,频谱形状,对一帧做FFT观察,应看到有效子载波带内平坦、带外因保护子载波而快速滚降,若带外泄漏明显,说明保护子载波留得不够或存在未置零的直流分量。
另一个容易被忽略的检查点是循环前缀的正确性:把加CP后的符号末尾 \(N\) 点取出做FFT,应与原始 \(X[k]\) 在数值上一致(在浮点误差范围内)。这一步能同时验证IFFT归一化、CP复制位置和索引是否写错,是调试发射链路时性价比最高的自检。
至此,发射端基带信号已经具备完整的帧形态:频域上数据、导频、保护各就其位,时域上每个符号带CP,整帧以前导开头。它既是后续信道仿真和接收端处理的输入,也是判断系统参数设计是否合理的第一道关口。