STBC MIMO-OFDM误码率分析:Alamouti编码与仿真避坑指南
简介:这套Matlab仿真代码实现基于STBC的MIMO-OFDM系统误码率分析,面向无线通信方向的学生与科研人员,重点解决多天线OFDM系统在不同调制方式下的性能评估问题。代码支持BPSK、QPSK、8PSK、16PSK四种M-PSK调制,并可在1X1、2X2、4X4三种天线配置间切换;发射端对随机数据先进行OFDM调制,再对每个子载波实施STBC编码,经过AWGN信道后,接收端完成STBC译码与OFDM解调,将恢复数据与原始发送序列比对统计误码率,并以蒙特卡洛仿真得到系统平均性能。资源压缩包共10个文件,包含5个Matlab脚本(对应不同调制方式的仿真程序及训练符号生成)、4张仿真结果图(展示不同调制和天线数下的误码率曲线)和1个关键词说明文件,整体仅163KB,适用于Matlab2016b环境,运行相应脚本即可直接出图。目前已有350人学习下载。代码结构完整、参数便于修改,适合作为课程设计、课题仿真或论文验证的参考实现,也能帮助初学者快速掌握MIMO-OFDM与STBC联合仿真的完整流程。
1. 为什么 STBC 的 MIMO-OFDM 系统误码率分析总在细节上翻车
不少人在调试基于 STBC 的 MIMO-OFDM 系统的误码率分析时,都会碰到一个反直觉的现象:明明给发射端加了两根天线,BER 却和单天线差不多,甚至在高信噪比段出现平台。问题通常不在 STBC 本身,而在子载波映射、信道功率归一化和蒙特卡洛终止条件这些细节上。STBC 用 Alamouti 空时分组码把符号映射到时间和空间两个维度,OFDM 再把宽带频率选择性信道拆成一组平坦子信道,两者结合的目的是让每条数据流获得 2 阶分集增益。这篇文章面向要动手搭链路仿真、写课程设计或者验证无线算法的工程师,从信号模型、Python 仿真代码、参数设置到四个高频踩坑点,完整走一遍误码率分析流程。
2. STBC 与 MIMO-OFDM 的信号模型:先把 Alamouti 和子载波映射讲透
2.1 Alamouti 编码:2x1 发射分集的核心矩阵
Alamouti 编码是 STBC 里最经典的 2x1 方案,两根发射天线、一根接收天线,在两个连续符号时隙内发送两个符号 x1 和 x2。天线 1 在时隙 1 发 x1,时隙 2 发 -x2*;天线 2 在时隙 1 发 x2,时隙 2 发 x1*。把行方向看成天线、列方向看成时隙,得到的编码矩阵是正交的,所以接收端不需要穷举搜索,用线性合并就能把 x1 和 x2 分开。
这个正交性是整个 STBC 误码率分析的基石。由于两列向量正交,两个符号经过信道后虽然混叠在同一个接收信号里,但各自携带的是两条独立路径的能量之和。在 Rayleigh 衰落信道里,单根天线的符号信噪比是单指数分布,BER 随 SNR 下降的斜率对应 1 阶分集;两条独立路径让等效信噪比变成两个指数变量的和,高 SNR 段的 log-log 斜率会被推到 2 阶分集。
我一般会在仿真里把 1x1 和 2x1 同时跑出来对比。如果两者的 BER 曲线斜率一致,说明 Alamouti 的正交合并结构没起作用,问题多半出在编码位置或信道假设上。Alamouti 有一个前提:信道在两个连续时隙内保持不变。OFDM 符号周期一般远大于符号速率,所以在慢衰落信道里这个假设基本成立,但在快衰落场景下不能直接套用,后面避坑部分会展开。
2.2 OFDM 的作用:把频率选择性信道拆成一组平坦衰落子信道
MIMO-OFDM 里的 OFDM 不是装饰。宽带信号经过多径信道后会出现频率选择性衰落,时域上表现为严重的符号间干扰。OFDM 把整个宽带分成 N 个子载波,每个子载波带宽小于信道相干带宽,于是每个子载波上的信道近似平坦。加上循环前缀 CP,只要 CP 长度大于最大时延扩展,多径时延导致的符号间干扰和子载波间干扰都会被吸收。
从基带等效角度看,OFDM 把时域卷积变成了频域逐点相乘:
Y(k) = H(k) * X(k) + N(k)
这里 H(k) 是第 k 个子载波上的复增益。每根发射天线到接收天线之间的链路都可以这样表示。所以在做误码率分析时,我们不需要在时域里逐抽头处理卷积,而是直接以子载波为单位去看 STBC 的合并效果。这也是为什么仿真代码里可以先用 IFFT 生成时域信号,再用 FFT 恢复频域,中间的信道操作等效到频域完成。
OFDM 的另一个作用是让 STBC 能同时拿到空间分集和频率分集。STBC 在频域逐子载波独立编码,每个子载波经历的信道衰落不同,只要子载波间隔大于相干带宽,多个子载波上的错误事件就不完全相关,整体误码率还能进一步被平均掉。这个效果在论文里常被归入频率分集,实际仿真时不需要特意处理,只要信道多径抽头足够多,自然会出现。
2.3 逐子载波的 STBC 映射:编码在频域完成,不在时域
这是 MIMO-OFDM 里最容易出错的一个点。把 Alamouti 直接套在 OFDM 时域采样点上是不对的。OFDM 符号是整体做 IFFT 生成的,时域采样点之间并没有子载波概念,两个时域采样点之间的 Alamouti 结构经过 IFFT 后会在频域被打散。正确的做法是:把频域符号矩阵按子载波对齐,取第 k 个子载波上两个连续的 OFDM 符号构成一组 Alamouti 码块。
我用一个表格把映射关系说清楚。假设 x1(k)、x2(k) 是第 k 个子载波上两个连续的 QPSK 符号,那么两个发射天线在两个 OFDM 符号时隙上发送的信号如下所示。
| 子载波 k | OFDM 时隙 1 | OFDM 时隙 2 |
|---|---|---|
| 天线 1 | x1(k) | -x2*(k) |
| 天线 2 | x2(k) | x1*(k) |
代码实现上,我会先把整个符号矩阵按行拆成偶数索引块和奇数索引块。偶数块作为 x1,奇数块作为 x2,然后分别映射到天线 1、天线 2 的两个时隙。这样每个子载波都独立完成了一次 Alamouti 编码,接收端也逐子载波做合并。
需要注意的是,如果信道在两个 OFDM 符号之间变化,正交性会被破坏,线性合并解不再是最优的。实际系统里常通过导频估计每个时隙的信道,然后用估计值代替真实信道。仿真时为了对比理论性能,通常先假设理想信道估计,再加信道估计模块看性能损失。
2.4 接收端的最大似然合并与等效信噪比
接收端两个时隙收到的频域信号可以写成:
R1(k) = H1(k)·x1(k) + H2(k)·x2(k) + N1(k)
R2(k) = -H1(k)·x2*(k) + H2(k)·x1*(k) + N2(k)
其中 H1(k)、H2(k) 分别是天线 1、天线 2 到接收天线在第 k 个子载波上的信道频响。理想信道估计下,线性合并公式是:
y1(k) = H1*(k)·R1(k) + H2(k)·R2*(k)
y2(k) = H2*(k)·R1(k) - H1(k)·R2*(k)
代入 R1、R2 后,交叉项会全部消掉,得到:
y1(k) = (|H1(k)|² + |H2(k)|²)·x1(k) + N1'(k)
y2(k) = (|H1(k)|² + |H2(k)|²)·x2(k) + N2'(k)
也就是说,每个符号的等效增益都是两条信道功率之和。因为合并后的信道增益是非负实数,QPSK 判决时不需要除以增益,直接看 y1(k)、y2(k) 的实部和虚部符号就可以恢复比特,这就是为什么仿真代码能把判决写得非常简洁。
从这个表达式也能看出 STBC 的一个特性:它带来的是分集增益而非阵列增益。2x1 Alamouti 相对 1x1 的增益来自两条独立路径的概率特性,而不是把发射功率加倍。如果仿真的 2x1 比 1x1 高了 3 dB,先检查是不是发射功率归一化出了问题。
3. 用 Python 搭一套 STBC MIMO-OFDM 误码率仿真链路
3.1 参数设置与符号结构:先把仿真脚本的骨架立起来
仿真链路我用 Python 加 NumPy,不依赖通信工具箱,这样读者在任何环境里都能跑。核心参数有子载波数、循环前缀长度、多径抽头数、调制阶数和蒙特卡洛终止条件。下面这段代码定义参数和 QPSK 调制函数。
import numpy as np # OFDM 仿真参数 N_SC = 64 # 子载波数 CP_LEN = 16 # 循环前缀长度,必须大于最大时延抽头数 N_TX = 2 # 发射天线数(Alamouti 需要 2) N_RX = 1 # 接收天线数 L_CH = 4 # 多径信道抽头数,最大时延为 L_CH-1 个采样间隔 P_TOTAL = 1.0 # 总发射功率约束,两根天线各分一半 N_OFDM_SYM = 512 # 每轮蒙特卡洛的 OFDM 符号数,保持偶数 SNR_MIN, SNR_MAX, SNR_STEP = 0, 24, 3 def qpsk_mod(bits): # 每 2 比特映射一个 QPSK 符号,Gray 映射:0->+,1->- bits = bits.reshape(-1, 2) b0, b1 = bits[:, 0], bits[:, 1] s = (2 * b0 - 1) + 1j * (2 * b1 - 1) return s / np.sqrt(2) # 星座点能量归一化到 1逻辑说明:qpsk_mod 把输入的比特流每两位一组映射成星座点。实部对应 b0,虚部对应 b1,00 映射到 -1-1j,01 映射到 -1+1j,依此类推。除以 sqrt(2) 是让每个符号的平均能量为 1,这样后面噪声方差可以直接按 Es/N0 定义,N0 = 10^(-SNR_dB/10)。
参数说明:N_SC 选 64 是为了仿真速度,实际系统常见 512 或 1024,规律一致。CP_LEN 必须大于 L_CH-1,这里 16 对 4 抽头信道绰绰有余。N_OFDM_SYM 取偶数是因为 Alamouti 编码要把符号矩阵按行拆成两份,如果总数是奇数,最后一行没法配对。如果你改子载波数,记得把 IFFT 和 FFT 的大小一起改,否则频域点数不匹配。
3.2 发射机实现:QPSK 符号的 Alamouti 编码与 OFDM 调制
发射机分成两步:先做频域的 Alamouti 编码,再对每根天线的每个时隙做 IFFT 加循环前缀。关键的功率归一化放在编码阶段完成,因为两根天线同时发送,每根天线只分得总发射功率的一半。
def alamouti_encode(sym_block): # sym_block: (N_OFDM_SYM, N_SC) 频域 QPSK 符号 even = sym_block[0::2, :] # 时隙 1 的符号,即 x1 odd = sym_block[1::2, :] # 时隙 2 的符号,即 x2 tx1_slot1 = even * np.sqrt(P_TOTAL / 2) tx1_slot2 = -np.conj(odd) * np.sqrt(P_TOTAL / 2) tx2_slot1 = odd * np.sqrt(P_TOTAL / 2) tx2_slot2 = np.conj(even) * np.sqrt(P_TOTAL / 2) return tx1_slot1, tx1_slot2, tx2_slot1, tx2_slot2 def ofdm_mod(freq_grid): # 频域符号 -> 时域 OFDM 基带信号,每行一个 OFDM 符号 n_sym = freq_grid.shape[0] t = np.fft.ifft(freq_grid, axis=1) * np.sqrt(N_SC) cp = t[:, -CP_LEN:].copy() return np.hstack([cp, t])逻辑说明:alamouti_encode 把频域符号矩阵按行奇偶拆分,偶数行作为 x1,奇数行作为 x2。天线 1 在时隙 1 发 even,时隙 2 发 -odd 的共轭;天线 2 在时隙 1 发 odd,时隙 2 发 even 的共轭。这与 Alamouti 编码矩阵完全一致。所有符号在编码时乘了 sqrt(P_TOTAL/2),确保两根天线总发射功率等于 1,和 1x1 系统对比时是公平的。
ofdm_mod 对每一行做逆傅里叶变换,乘 sqrt(N_SC) 是能量归一化,让时域信号的平均功率等于频域符号的平均功率。循环前缀直接取每个 OFDM 符号最后 CP_LEN 个采样拼到前面,这是为了把多径信道的线性卷积变成循环卷积,接收端才能用 FFT 恢复。
参数说明:P_TOTAL/2 的归一化方向不能搞反。如果每根天线都按满功率 1 归一化,2x1 系统总发射功率就是单天线的两倍,BER 曲线会比公平对比好大约 3 dB,这个增益是功率翻倍带来的,不是分集增益。在论文图表里如果明确说明是每天线功率,可以这么归一化,但自对比时一定要统一口径。
3.3 多径信道与接收端:把循环前缀吃掉,再做 FFT 和 Alamouti 合并
信道模型用多径 Rayleigh 衰落。为了仿真稳定,每条收发链路的抽头总功率归一化到 1。接收端先做时域卷积,然后丢弃循环前缀,取 FFT 窗口内的采样恢复频域信号,再做 Alamouti 线性合并。
def gen_channel(n_tx, L=L_CH): # 生成 n_tx 条独立多径信道,每条总功率归一化到 1 h = [] for _ in range(n_tx): taps = (np.random.randn(L) + 1j * np.random.randn(L)) / np.sqrt(2) taps /= np.sqrt(L) h.append(taps) return np.array(h) # (n_tx, L) def channel_conv(tx_sym, h): # tx_sym: (n_slot, N_SC + CP_LEN) 时域发送信号 n_slot = tx_sym.shape[0] y = np.zeros((n_slot, N_SC), dtype=complex) for i in range(n_slot): full = np.convolve(tx_sym[i], h) y[i] = full[CP_LEN:CP_LEN + N_SC] # 丢掉 CP,截取 FFT 窗口 return y逻辑说明:gen_channel 生成复高斯抽头,每径功率都相同,最后除以 sqrt(L) 让所有抽头功率之和为 1。这样信道频响的平均功率也为 1,接收端加噪声时不需要再额外修正信道增益。channel_conv 把每个 OFDM 符号和信道冲激响应做线性卷积,然后截取 CP_LEN 到 CP_LEN+N_SC 这一段进入 FFT 窗口。
这段代码的截取方式值得展开讲一下。线性卷积完的序列长度是 N_SC+CP_LEN+L_CH-1。丢掉前 CP_LEN 个采样后,剩下的就是当前 OFDM 符号数据部分和一部分信道拖尾的叠加。由于 CP 长度大于最大时延,这个叠加恰好等价于频域相乘,所以后面做 FFT 得到的就是 H(k) 乘以发射频域符号。如果 CP 不够长,这段窗口里会混入上一个符号的残留,BER 就会在高 SNR 段出现平台。
在频域信道响应的计算上,我对每个抽头向量补零到 N_SC 再做 FFT,得到 H1、H2。由于抽头总功率为 1,频响的平均功率也是 1,噪声方差可以按 N0 = 10^(-SNR/10) 直接加在时域或频域。
3.4 跑通最小示例:完整的 BER 统计主循环
主循环里每一轮生成一批随机比特,调制、编码、过信道、合并、判决,然后累计错误比特。蒙特卡洛终止条件用两个:统计比特数下限和错误比特数下限,这样高 SNR 段不会白跑,低 SNR 段也不会因为样本太少导致曲线毛刺太大。
def run_ber(snr_db): n0 = 10 ** (-snr_db / 10) err_cnt, bit_cnt = 0, 0 while bit_cnt < 2_000_000 and err_cnt < 200: # 生成随机比特并调制 bits = np.random.randint(0, 2, N_OFDM_SYM * N_SC * 2) sym = qpsk_mod(bits).reshape(N_OFDM_SYM, N_SC) tx1_s1, tx1_s2, tx2_s1, tx2_s2 = alamouti_encode(sym) # 生成并计算信道频响 h1 = gen_channel(1)[0] h2 = gen_channel(1)[0] H1 = np.fft.fft(h1, N_SC) H2 = np.fft.fft(h2, N_SC) # 发射端时域信号 tx1_s1_t = ofdm_mod(tx1_s1) tx1_s2_t = ofdm_mod(tx1_s2) tx2_s1_t = ofdm_mod(tx2_s1) tx2_s2_t = ofdm_mod(tx2_s2) # 接收端:时域卷积后加高斯白噪声 rx1_t = channel_conv(tx1_s1_t, h1) + channel_conv(tx2_s1_t, h2) rx2_t = channel_conv(tx1_s2_t, h1) + channel_conv(tx2_s2_t, h2) noise1 = np.sqrt(n0 / 2) * (np.random.randn(*rx1_t.shape) + 1j * np.random.randn(*rx1_t.shape)) noise2 = np.sqrt(n0 / 2) * (np.random.randn(*rx2_t.shape) + 1j * np.random.randn(*rx2_t.shape)) rx1_f = np.fft.fft(rx1_t + noise1, axis=1) / np.sqrt(N_SC) rx2_f = np.fft.fft(rx2_t + noise2, axis=1) / np.sqrt(N_SC) # Alamouti 线性合并 y1 = np.conj(H1) * rx1_f + H2 * np.conj(rx2_f) y2 = np.conj(H2) * rx1_f - H1 * np.conj(rx2_f) # 逐比特判决并统计错误 x1 = sym[0::2, :] x2 = sym[1::2, :] b0_1 = (np.real(x1) > 0).astype(int) b1_1 = (np.imag(x1) > 0).astype(int) b0_2 = (np.real(x2) > 0).astype(int) b1_2 = (np.imag(x2) > 0).astype(int) d0_1 = (np.real(y1) > 0).astype(int) d1_1 = (np.imag(y1) > 0).astype(int) d0_2 = (np.real(y2) > 0).astype(int) d1_2 = (np.imag(y2) > 0).astype(int) err_cnt += int(np.sum(d0_1 != b0_1) + np.sum(d1_1 != b1_1) + np.sum(d0_2 != b0_2) + np.sum(d1_2 != b1_2)) bit_cnt += N_OFDM_SYM * N_SC * 2 return err_cnt / bit_cnt for snr_db in range(SNR_MIN, SNR_MAX + 1, SNR_STEP): ber = run_ber(snr_db) print(f"SNR={snr_db:2d} dB BER={ber:.3e}")逻辑说明:每轮 while 循环重新生成一组信道实现,代表一帧数据经历的慢变衰落。发射端对两个时隙的信号分别做 OFDM 调制,接收端把两路信号叠加后分别过各自的卷积信道。噪声加在时域,方差为 n0,分为实部和虚部各 n0/2。FFT 后除以 sqrt(N_SC) 是为了和发射端的 IFFT 归一化对齐。
合并部分直接照搬上一章推导的公式。y1、y2 中交叉项消掉后,QPSK 判决只需要判断实虚部符号,不需要除以 |H1|²+|H2|²,因为非负实数的缩放不改变星座点所在象限。这也避免了除以小信道增益时放大噪声的数值问题。
参数说明:err_cnt 达到 200 就提前终止,这是为了让高 SNR 段不至于为了多统计几个零头跑几十分钟。bit_cnt 下限设 2 百万,对应的最低 BER 分辨率大约 5e-7,足够看 1e-4 到 1e-5 这个区间。如果你要画 1e-6 以下的深度曲线,把下限提高到 1e7 或 5e7,同时把 err_cnt 下限也向上调。
4. 误码率分析避坑:四次翻车记录与排查方法
4.1 高 SNR 段 BER 平台不下降,曲线躺着不动
现象:BER 在 10 dB 之前下降正常,但到了 18 dB 之后几乎不再变化,曲线出现明显的 error floor。
原因:最常见的元凶是循环前缀长度不够。多径信道的最大时延抽头数超过 CP_LEN,上一个 OFDM 符号的拖尾会落进当前符号的 FFT 窗口,破坏子载波正交性,产生无法通过提高 SNR 消除的干扰平台。另一个可能性是仿真里忘了加频偏补偿,但在基带等效仿真里一般不会出现这个,优先查 CP。
解决:检查 L_CH 和 CP_LEN 的约束关系,严格满足 CP_LEN >= L_CH - 1。如果信道用了指数衰减功率时延谱,最大时延以功率衰减到噪声底为准。你可以做个对照实验:把 L_CH 改成 2,CP_LEN 保持 16,BER 曲线平台应该立刻消失。如果平台还在,再检查噪声是否被错误地加在了频域两次。
4.2 加了 STBC,分集增益却为零
现象:2x1 的 BER 曲线和 1x1 几乎重合,高 SNR 段斜率一致,没有出现分集增益。
原因:多半是把 Alamouti 编码用在了时域采样点上。比如把两个连续的时域采样值做配对编码,再送入 OFDM 调制。时域编码经过 IFFT 后,频域符号已经被打散,Alamouti 正交结构只存在于编码端,接收端在频域无法对应回去。
解决:回到 2.3 节的映射表,确认编码发生在频域符号矩阵上。具体到代码,就是先对 QPSK 符号矩阵做行拆分成 even 和 odd,再构造两个天线的频域信号,最后才对频域矩阵做 IFFT。判断方法很简单:把 alamouti_encode 的输入和输出打印出来,检查 tx1_slot2 是否等于 -conj(odd),tx2_slot2 是否等于 conj(even)。如果编码后直接再做一次 FFT,你应该看到所有子载波上的 Alamouti 结构完好保留。
4.3 仿真 BER 和理论曲线系统性偏移几个 dB
现象:BER 曲线的形状和斜率都正常,但整体平移了 2 到 3 个 dB,或者低 SNR 段就对不上。
原因:功率归一化问题。比较常见的有两种:第一种是多径抽头没有归一化,gen_channel 里少了除以 sqrt(L),导致每条路径都是满功率,接收信号能量被放大,等效 SNR 虚高;第二种是两根发射天线各自按功率 1 归一化,2x1 的总发射功率是 1x1 的两倍,白赚了 3 dB。
解决:统一口径。发射端用 P_TOTAL=1.0,每根天线乘 sqrt(P_TOTAL/2);信道抽头生成后除以 sqrt(L),保证 E[sum(|h|²)] = 1。如果要在论文里明确对比每天线功率方案,可以把 P_TOTAL 改成 2.0,并在图注里写清楚。一个更稳妥的验证方法是把 Alamouti 模块去掉,直接跑 1x1 的 QPSK-OFDM,和已知的 Rayleigh 平坦衰落理论曲线对比,先把单链路校准到一致,再跑 2x1。
4.4 低 SNR 段 BER 抖动大,两次运行结果不一致
现象:0 到 6 dB 段的 BER 点忽高忽低,甚至出现曲线交叉,同样的参数跑两次差别明显。
原因:蒙特卡洛样本不足。低 SNR 段误码率高,错误比特数其实不少,但因为每轮信道实现更新的粒度太大或者总比特数太少,抽样方差直接体现在 BER 点上。另一种情况是 while 循环的错误比特停止条件设得太低,比如 100 个错误就停,BER 在 1e-3 以上时相对误差仍然很大。
解决:给蒙特卡洛统计加上双阈值。我把 err_cnt 下限提到 200,bit_cnt 下限提到 2 百万,跑出来的曲线就平滑很多。如果还要更稳,可以在每个 SNR 点重复跑 3 次取平均,或者把信道更新粒度改小:每轮 while 只跑 N_OFDM_SYM=128 个符号,但让循环多跑几十轮,信道随时间变化更充分,能降低信道抽样带来的波动。
5. 从 BER 曲线斜率验证分集阶数:一个不依赖理论公式的校验技巧
5.1 用 1x1 对比 2x1,看 log-log 斜率
很多入门材料会直接给 Rayleigh 信道下的理论 BER 公式,但公式的前提条件和仿真假设未必完全一致,硬套反而容易出问题。我更推荐一个纯经验校验法:画出 1x1 和 2x1 的 BER 曲线,横轴 SNR 用 dB,纵轴 BER 用对数坐标。在误码率低于 1e-3 的区间里,两条曲线近似是直线,斜率就对应分集阶数。
1x1 的斜率约为 -1,意思是 SNR 每提升 10 dB,BER 大约下降一个数量级。2x1 Alamouti 的斜率约为 -2,同样是 10 dB,BER 下降两个数量级。判断方法不用拟合,直接看同样的 SNR 跨度下 BER 下降的倍数差。比如某个仿真配置里,1x1 从 20 dB 到 24 dB 下降了约 4 倍,2x1 就应该下降到约 16 倍左右。做不到这个倍数关系,说明某条链路没有真正提供两条独立衰落路径。
这里有个容易混淆的点:分集阶数只决定斜率,不决定绝对增益。2x1 曲线在低 SNR 段可能和 1x1 靠得很近,这是正常的,因为分集增益主要在深衰落概率上体现。如果你的曲线从低 SNR 开始就整体平移很多,大概率是功率归一化问题,回到 4.3 检查。
5.2 信道估计与导频间隔的经验值
上面的仿真假设理想信道估计,实际系统必须用导频估计 H1、H2。常见做法是梳状导频加线性插值。导频间隔多少合适取决于信道频域相关性:多径时延扩展为 L 个采样时,相邻子载波的信道相关系数随间隔增大而下降,我一般把导频间隔控制在 4 到 8 个子载波,做线性插值后 BER 相对理想信道估计损失在 0.5 dB 以内。间隔超过 16 后,深衰落子载波处的插值误差明显增大,高 SNR 段会再次出现平台。
如果用 LS 估计,导频位置的噪声会被直接带入插值后的信道响应。一个简单有效的改进是先在频域做个滑动平均再插值,窗口大小取 3 到 5 个子载波,能把估计噪声压低不少,代价是频率选择性的边缘有点被抹平。实际调试时我习惯把理想信道估计和 LS 估计两条曲线画在一起,如果两者落差大于 1 dB,先怀疑导频功率、导频间隔和插值方法,而不是编码本身。
我自己的血泪经验是:第一次跑 STBC MIMO-OFDM 链路时,BER 曲线一直和单天线重合,后来一行行检查才发现编码写在了 IFFT 之后。把编码挪到频域,再把发射功率归一化改成总功率约束,曲线才终于出现 2 阶斜率。这个链路本身不复杂,但每个环节的归一化和映射都牵一发动全身。希望这套仿真框架和排查思路能帮你少走这些弯路。
本文还有配套的精品资源,点击获取