简介:面向数字通信与无线通信研究者的OTFS完整实现代码包,覆盖OTFS调制解调全流程以及接收端匹配滤波设计,可支撑高速移动场景下的物理层仿真与算法验证。压缩包共7个文件,包含6个MATLAB脚本和1篇英文论文,整体仅988KB;脚本按调制、信道生成、信道输出、解调及消息传递检测等环节拆分,结构清晰,便于单步复现与二次开发。代码从符号映射、ISFFT变换、加循环前缀,到接收端SFFT恢复、信道估计/补偿与匹配滤波均有对应实现,配合PDF中关于干扰消除与迭代检测的原理讲解,可帮助快速理解OTFS时频域处理机制。已有3992人学习/浏览,适合正在研究OTFS、OFDM对比或高速移动通信物理层算法的开发者参考,也可作为课程设计或论文实验的基础代码,帮助初学者快速建立OTFS整体认知。
1. OTFS完整代码例程:延迟-多普勒域的调制解调与匹配滤波实现
无数做数字通信仿真的人都有过这种体验:把手上的OFDM链路调通了,误码率曲线也正常往下掉,但一到高移动性场景就露馅,多普勒频偏把子载波正交性冲得七零八落。这套OTFS完整代码例程解决的就是这个问题——它把调制解调的全过程从时频域搬到延迟-多普勒域,通过逆辛傅里叶变换将QAM符号铺到二维网格上,再配合接收端的匹配滤波,在高移动性信道下依然能保持稳定的解调性能。整个例程覆盖了从发射端QAM映射、ISFFT变换、海森堡变换到接收端维格纳变换、SFFT恢复和匹配滤波输出的完整链路,适合正在做数字通信物理层仿真、希望理解OTFS与OFDM差异、或者需要一份能直接跑通的OTFS参考实现的开发者。
2. 系统架构与技术选型:从时频域搬到延迟-多普勒域的链路设计
2.1 系统链路总览:发射端到接收端的完整信号流
OTFS的核心思路是把信息符号承载在延迟-多普勒域,而不是传统的时频域。整套例程的发射端链路包含四个主要阶段:QAM符号映射、逆辛傅里叶变换(ISFFT)、海森堡变换(Heisenberg Transform)以及加循环前缀后的并串转换。接收端则是镜像过程:串并转换去循环前缀、维格纳变换(Wigner Transform)、辛傅里叶变换(SFFT),最后进入匹配滤波器和QAM逆映射。
信号流大致是这样走的:二进制比特流先按设定的调制阶数映射为QAM符号,这些符号排列成一个M×N的二维网格,M对应延迟维度(子载波数),N对应多普勒维度(符号数)。ISFFT把这个网格从延迟-多普勒域变换到时间-频率域,得到X[n,m]矩阵,再经过海森堡变换(本质是一组脉冲成形的OFDM调制)生成时域发射信号s(t)。接收端收到r(t)后,先做维格纳变换得到时频域接收符号Y[n,m],再做SFFT得到延迟-多普勒域的接收符号矩阵,最后过匹配滤波器提取每个网格点上的能量,送入QAM解映射。
这套架构里最值得注意的设计是:发射端的ISFFT和接收端的SFFT不是简单的傅里叶变换对,而是辛傅里叶变换,它同时沿时延维度和多普勒维度做DFT,因此能把信道的时变多径效应在变换域中表达为稀疏的抽头响应,这是OTFS对抗高多普勒的基础。
2.2 技术选型分析:OTFS与OFDM的定位差异
很多刚接触OTFS的开发者会问:既然OFDM已经广泛应用,为什么还需要OTFS?这套例程的设计定位和OFDM有本质区别。OFDM把宽带信道划分为多个窄带子载波,每个子载波经历平坦衰落,循环前缀吸收符号间干扰,但在高移动性场景下,多普勒频偏破坏了子载波之间的正交性,载波间干扰成为主要瓶颈。
OTFS把信息调制在延迟-多普勒域,信道在这个域中是时不变的,每个符号经历相同的信道增益。换句话说,OTFS把时变衰落信道转换成了二维的时不变信道,代价是接收端需要更复杂的均衡和匹配滤波处理。从实现复杂度看,OTFS的收发链路和OFDM有很多共用模块——海森堡变换本质就是加窗OFDM调制,维格纳变换本质是加窗OFDM解调,所以在一个OFDM链路上扩展OTFS比从零搭建要快得多。这套例程里保留了这种共性,代码里能看到OFDM调制解调的基础操作被复用为OTFS的海森堡和维格纳变换,只是增加了ISFFT和SFFT的处理环节。
从仿真结果看,在同样的信噪比条件下,OTFS在低速场景下的误码率性能和OFDM接近,但在多普勒频移较大的场景下优势明显。例程里默认配置了多径衰落信道,设置了多普勒频移参数,跑一次对比就能直观看到这个差异。
2.3 工程实现结构:模块化划分与仿真流程
这套例程在工程上采用模块化组织,每个功能独立成函数,主脚本负责串联整个仿真流程。目录结构大致分为发射端模块、信道模块、接收端模块和工具函数四块。发射端模块包含QAM映射函数、ISFFT实现、海森堡变换和脉冲成形;信道模块负责生成多径抽头系数、多普勒频移和加性高斯白噪声;接收端模块包含维格纳变换、SFFT、匹配滤波器和QAM解映射。
主仿真脚本的循环结构通常是这样的:遍历多个信噪比点,在每个信噪比下循环发送多帧数据,统计误比特数和误符号数,最终绘制误码率曲线。这种结构与大多数通信系统仿真工程一致,好处是每个模块可以单独验证——你可以单独调用ISFFT函数观察输出的时频域网格形状,也可以单独跑匹配滤波器对比滤波前后的星座图收敛效果。
3. 核心模块拆解:调制端、信道端与接收端匹配滤波的完整实现
3.1 发射链路:QAM映射、ISFFT与海森堡变换
发射端的第一步是把随机二进制比特流映射为QAM符号。例程里默认使用16QAM,可以根据需要切换QPSK、64QAM等阶数。QAM映射的核心是查表操作,给定调制阶数后生成对应的符号星座点。
def qam_mapping(bits, M): """将比特流映射为QAM符号 Args: bits: 二进制比特流,长度需为 log2(M) 的整数倍 M: 调制阶数,16或64 Returns: symbols: 复数QAM符号序列 """ k = int(math.log2(M)) # 每k个比特为一组,映射到星座点 symbols = [] for i in range(0, len(bits) - k + 1, k): group = bits[i:i+k] # 前一半比特决定实部,后一半决定虚部 real_bits = group[:k//2] imag_bits = group[k//2:] real_val = sum(b << j for j, b in enumerate(real_bits)) imag_val = sum(b << j for j, b in enumerate(imag_bits)) # 映射到QAM星座点,归一化功率 real = (2 * real_val - (math.sqrt(M) - 1)) / math.sqrt(2 * (M - 1) / 3) imag = (2 * imag_val - (math.sqrt(M) - 1)) / math.sqrt(2 * (M - 1) / 3) symbols.append(complex(real, imag)) return np.array(symbols)这里注意功率归一化因子,它保证了不同调制阶数下符号的平均功率一致,这样后续比较不同阶数的误码率曲线时才有可比性。QAM映射完成后,符号被排列成M×N的矩阵X,行索引对应延迟维度,列索引对应多普勒维度,这个矩阵就是ISFFT的输入。
ISFFT的实现是OTFS区别于OFDM的关键一步。它沿矩阵的行和列分别做DFT和IDFT:延迟维度做DFT,多普勒维度做IDFT,这样变换后的矩阵X_tf就落在时频域网格上。
def isfft(X_dd, M, N): """逆辛傅里叶变换:延迟-多普勒域 -> 时间-频率域 Args: X_dd: M×N的延迟-多普勒域符号矩阵 M: 延迟维度大小(子载波数) N: 多普勒维度大小(符号数) Returns: X_tf: M×N的时间-频率域符号矩阵 """ # 沿延迟维度(行方向)做DFT X_tf = np.fft.fft(X_dd, axis=0) # 沿多普勒维度(列方向)做IDFT X_tf = np.fft.ifft(X_tf, axis=1) # 归一化保持能量 X_tf = X_tf * math.sqrt(M * N) return X_tf参数说明:M对应子载波数,N对应OTFS符号数。Fourier变换的方向选择是有讲究的——沿延迟维度做FFT得到频域表达,沿多普勒维度做IFFT得到时域表达,这与无线信道的物理特性对应。很多刚上手的人容易在这里搞反方向,导致接收端解出来的星座图乱成一团。
海森堡变换把时频域符号X_tf转换为时域发射信号。它本质上是把每个时频网格点上的符号,通过脉冲成形滤波器调制到对应的时频位置:
def heisenberg_transform(X_tf, tx_pulse, M, N): """海森堡变换:时间-频率域 -> 时域发射信号 Args: X_tf: M×N的时频域符号矩阵 tx_pulse: 发射脉冲成形滤波器,通常是升余弦或矩形窗 M, N: 网格维度 Returns: s_t: 时域发射信号序列 """ s_t = np.zeros(M * N + len(tx_pulse) - 1, dtype=complex) for n in range(N): for m in range(M): # 每个网格点对应的时域起始位置 start = n * M + m # 用脉冲成形滤波器调制符号并叠加 s_t[start:start + len(tx_pulse)] += X_tf[m, n] * tx_pulse # 添加循环前缀,长度通常取信道最大时延的1~2倍 cp_len = M // 4 s_t_cp = np.concatenate([s_t[-cp_len:], s_t]) return s_t_cp海森堡变换的双重循环在M和N较大时效率不高,常见做法是用矩阵乘法一次性完成。例程里保留循环写法是为了让原理更直观,实际工程中一般会改成基于FFT的快速实现。循环前缀的取值会影响系统开销——取太短吸收不了多径扩展,取太长浪费频谱效率,M//4这个比例是时延扩展和频谱效率之间的常见折中。
3.2 信道建模:延迟-多普勒域的抽头参数生成
信道模块是这套例程里直接影响匹配滤波效果的部分。OTFS的优势建立在信道在延迟-多普勒域上具有稀疏性这个假设之上,因此信道建模需要生成有限数量的多径抽头,每个抽头有独立的时延、多普勒频移和衰减系数。
def generate_channel_taps(num_taps, max_delay, max_doppler, M, N, fs): """生成延迟-多普勒域信道抽头 Args: num_taps: 多径抽头数量 max_delay: 最大时延(秒) max_doppler: 最大多普勒频移(Hz) M, N: 网格维度 fs: 采样率(Hz) Returns: taps: 信道抽头列表,每个元素含delay_idx, doppler_idx, alpha """ taps = [] for _ in range(num_taps): # 时延映射到延迟维度索引,量化到最近的整数网格 delay_s = random.uniform(0, max_delay) delay_idx = int(round(delay_s * fs / M)) # 对偶:多普勒映射到多普勒维度索引 doppler_hz = random.uniform(-max_doppler, max_doppler) doppler_idx = int(round(doppler_hz * N / (fs / M * N))) # 每条路径的复衰减系数,幅度服从瑞利分布 alpha = np.random.rayleigh(1) * np.exp(1j * random.uniform(0, 2 * np.pi)) taps.append({'delay_idx': delay_idx, 'doppler_idx': doppler_idx, 'alpha': alpha}) return taps延迟索引的计算中,fs/M是子载波间隔,时延除以一个符号周期得到延迟维度的偏移量;多普勒索引则是多普勒频移乘以OTFS符号周期得到的归一化偏移。这两组索引的量化精度直接决定信道在延迟-多普勒域上是否稀疏——如果抽头落在网格点之间,能量会泄漏到相邻网格,降低匹配滤波器的性能。
生成信道抽头后,需要把时域发射信号通过信道。一种做法是直接在时域做卷积加频移,另一种是在延迟-多普勒域做乘性处理。例程采用时域实现,更贴近真实物理信道:
def apply_channel(s_t_cp, taps, M, N, fs, snr_db): """时域信道通过:多径卷积 + 多普勒频移 + AWGN Args: s_t_cp: 带循环前缀的时域发射信号 taps: 信道抽头列表 M, N: 网格维度 fs: 采样率 snr_db: 信噪比(dB) Returns: r_t_cp: 接收时域信号 h_eff: 等效信道矩阵(用于匹配滤波) """ # 每个抽头时延对应的采样点数 channel_len = M + 2 * N # 预留信道冲击响应长度 h_time = np.zeros(channel_len, dtype=complex) for tap in taps: # 将抽头放到对应的时延位置,并叠加多普勒相移 for n in range(N): idx = tap['delay_idx'] + n * M if idx < channel_len: phase = 2 * np.pi * tap['doppler_idx'] * n / N h_time[idx] += tap['alpha'] * np.exp(1j * phase) # 时域卷积通过信道 r_t = np.convolve(s_t_cp, h_time) # 加高斯白噪声,按符号功率计算 signal_power = np.mean(np.abs(r_t)**2) noise_power = signal_power / (10 ** (snr_db / 10)) noise = np.sqrt(noise_power / 2) * (np.random.randn(len(r_t)) + 1j * np.random.randn(len(r_t))) r_t_cp = r_t + noise return r_t_cp, h_time这个信道实现涵盖了两个关键效应:多径时延导致的时间弥散,以及多普勒频移导致的频率弥散。h_eff的构建方式值得留意——它不是把抽头简单地放在延迟索引处,而是展开了N个符号周期上的相移序列,这样匹配滤波时能正确地沿多普勒维度做相干积累。
3.3 接收端匹配滤波:维格纳变换、SFFT与相关检测
接收端的第一个模块是维格纳变换,它把接收信号从时域转回时频域。这一步与OFDM解调几乎一致:去循环前缀、串并转换、FFT。差别在于OTFS需要保留每个时频网格点上的完整信息,因此脉冲成形滤波器在接收端同样参与运算。
def wigner_transform(r_t_cp, rx_pulse, M, N): """维格纳变换:时域接收信号 -> 时间-频率域 Args: r_t_cp: 带循环前缀的接收信号 rx_pulse: 接收匹配脉冲,通常取发射脉冲的共轭反转 M, N: 网格维度 Returns: Y_tf: M×N的时频域接收符号矩阵 """ r_t = r_t_cp[M//4:] # 去循环前缀 Y_tf = np.zeros((M, N), dtype=complex) for n in range(N): block = r_t[n*M : (n+1)*M] # 与接收脉冲做相关,再FFT得到频域符号 filtered = np.convolve(block, rx_pulse, mode='same') Y_tf[:, n] = np.fft.fft(filtered) return Y_tf这里有个容易踩的细节:接收脉冲和发射脉冲要匹配,通常取发射脉冲的时间反转共轭,这样维格纳变换才是海森堡变换的最优逆操作。如果用不匹配的脉冲,接收端星座图会出现系统性畸变,这是OTFS例程调试里最常见的翻车原因之一。
SFFT把时频域符号矩阵转换回延迟-多普勒域,是发射端ISFFT的逆过程:
def sfft(Y_tf, M, N): """辛傅里叶变换:时间-频率域 -> 延迟-多普勒域 Args: Y_tf: M×N的时频域接收符号矩阵 M, N: 网格维度 Returns: Y_dd: M×N的延迟-多普勒域符号矩阵 """ # 沿多普勒维度做FFT Y_dd = np.fft.fft(Y_tf, axis=1) # 沿延迟维度做IFFT Y_dd = np.fft.ifft(Y_dd, axis=0) Y_dd = Y_dd * math.sqrt(M * N) return Y_dd匹配滤波器是这套例程里接收端的核心模块。它的作用是结合信道状态信息,对每个延迟-多普勒网格点上的符号做相干检测,最大化每个符号的SNR。在单抽头信道下,匹配滤波退化为简单的除法均衡;在多抽头信道下,它需要对信道响应做相关运算。
def matched_filter(Y_dd, h_eff, M, N): """匹配滤波检测:基于信道响应的共轭相关 Args: Y_dd: 延迟-多普勒域接收符号矩阵 h_eff: 等效信道矩阵 M, N: 网格维度 Returns: X_est: 估计的发射符号矩阵 """ X_est = np.zeros((M, N), dtype=complex) # 对每个网格点做信道共轭相关 for m in range(M): for n in range(N): # 找到信道响应的主要抽头位置 h_val = h_eff[m % len(h_eff), n % min(N, h_eff.shape[1])] if h_eff.ndim > 1 else h_eff[0] # 匹配滤波:乘信道共轭再归一化 X_est[m, n] = Y_dd[m, n] * np.conj(h_val) / (np.abs(h_val)**2 + 1e-6) # 对估计符号做QAM硬判决 X_demod = qam_demodulate(X_est, 16) return X_demod匹配滤波的本质是最小化噪声影响的同时保持信号能量最大。分母中加1e-6是为了避免信道响应趋近零时出现数值爆炸,这个正则化系数在低信噪比场景下要适当调大。实际项目中更常用的做法是用MMSE准则替代纯匹配滤波,在噪声增强和符号间干扰之间取得平衡——如果你发现高信噪比下的误码率曲线有一个下不去的平台,多半就是这里需要从匹配滤波切换到MMSE均衡。
4. 运行指南:环境准备、参数配置与结果验证
4.1 目录结构与运行入口
整套例程的目录结构很清晰,拿到手先看README和主脚本,再逐个模块读下去:
otfs_example/ ├── README.md ├── main_simulation.py # 主仿真脚本,循环信噪比点并统计误码率 ├── config.py # 全局参数配置 ├── tx/ │ ├── qam_mapper.py # QAM映射与逆映射 │ ├── isfft.py # 逆辛傅里叶变换 │ └── heisenberg.py # 海森堡变换与脉冲成形 ├── channel/ │ ├── channel_model.py # 多径抽头生成与时域卷积 │ └── noise.py # AWGN噪声叠加 ├── rx/ │ ├── wigner.py # 维格纳变换 │ ├── sfft.py # 辛傅里叶变换 │ └── matched_filter.py # 匹配滤波与QAM判决 └── utils/ ├── ber_calc.py # 误码率统计 └── plot_results.py # 结果可视化主脚本是运行入口,它读取config.py中的参数,驱动整个仿真流程。运行方式很简单,在项目根目录执行:
python main_simulation.py跑完会输出一个误码率曲线图和若干中间状态图,包括发射星座图、接收端匹配滤波前后的星座图对比、信道频率响应等。默认参数下整轮仿真大约需要几十秒到几分钟,取决于信噪比点数设置。
4.2 关键参数配置与调参建议
config.py里的参数直接决定仿真结果的正确性和运行时长。核心参数包括以下几组:
# 调制阶数与网格维度 MOD_ORDER = 16 # QAM阶数:4/16/64 M = 128 # 延迟维度(子载波数) N = 32 # 多普勒维度(OTFS符号数) # 信道参数 NUM_TAPS = 4 # 多径抽头数 MAX_DELAY = 5e-6 # 最大时延(秒),对应5微秒 MAX_DOPPLER = 500 # 最大多普勒频移(Hz) FS = 10e6 # 采样率(Hz) # 仿真控制 SNR_DB_LIST = [0, 5, 10, 15, 20, 25, 30] FRAMES_PER_SNR = 100 # 每个信噪比下的仿真帧数M和N的比例决定了OTFS符号的开销和抗多普勒性能。M越大,子载波间隔越小,对多普勒频移的容忍度越低;N越大,OTFS符号周期越长,延迟-多普勒分辨率的粒度越细。默认的128×32在高移动性场景下是合理配置,如果你要复现高铁场景(多普勒频移到1000Hz以上),建议把M降到64,N保持在32或64。
FRAMES_PER_SNR影响误码率统计的平滑度:帧数太少,曲线毛刺明显;帧数太多,运行时间成倍增加。100帧是一个折中值。调试阶段可以先设10帧快速验证流程,确认无误后再调回100帧出正式曲线。
另外要注意MAX_DELAY和循环前缀长度的匹配。循环前缀长度在发射端设定为M//4,对应时延上限是(M//4) / FS秒。如果MAX_DELAY超过这个值,多径分量会溢出循环前缀,带来无法消除的符号间干扰。调参时先算一下这个上限,避免跑出异常曲线。
4.3 结果验证:从误码率曲线到星座图检查
仿真跑完后,先看误码率曲线的整体趋势。在AWGN信道下,16QAM的误码率曲线应该平滑下降,大约在20dB左右达到10的负三次方量级。在多径信道下曲线会整体右移,这是正常的。如果曲线出现明显的错误平台——比如信噪比增加到30dB后误码率不再下降而是稳定在某个值——那通常是信道均衡不充分导致,需要检查匹配滤波是否漏掉了某个高能量抽头。
星座图检查有两个关键位置:发射端ISFFT前的QAM星座图应该干净整洁,16个点清晰分离;接收端匹配滤波后的星座图应该收敛到同样的16个点,只是有不同程度的散落。如果接收星座图出现旋转、重叠或系统性偏移,排查方向是:检查接收脉冲是否与发射脉冲匹配,检查SFFT的归一化因子,检查匹配滤波中信道估计是否准确。
主脚本里还输出了信道时频响应的可视化图,这个图能直观看出多径抽头在延迟-多普勒网格上的分布。如果抽头位置和generate_channel_taps里设置的参数不一致,多半是量化索引的计算公式有误——对照fs、M、N的单位换算再核对一遍。
5. 避坑与排查:OTFS例程里最常见的翻车现场
5.1 坑一:循环前缀长度不够,误码率曲线出现错误平台
现象:多径信道下误码率曲线在15dB后不再下降,形成一个水平平台,无论怎么增加信噪比都没改善。
原因:多径信道的最大时延超过了循环前缀的保护范围。默认循环前缀为M//4=32个采样点,按采样率10MHz计算,保护时长只有3.2微秒。当MAX_DELAY设置为5微秒时,第二条多径分量有一部分能量溢出循环前缀窗口,在OFDM/OTFS解调时造成符号间干扰,这个干扰是信号功率级的,不会被噪声淹没。
解决:把MAX_DELAY降到2微秒以内,或者把循环前缀加长到M//2。更通用的做法是写一个断言,在仿真开始前检查MAX_DELAY是否小于循环前缀时长:循环前缀采样点数除以采样率,得到的秒数必须大于最大时延。从那以后我每次改信道参数都先跑这个断言,翻车概率下降了一大截。
5.2 坑二:匹配滤波时延对齐偏移,自相关峰错位
现象:接收端星座图整体旋转了一个角度,16QAM的星座点看起来还在网格上但全部偏离理想位置,误码率高得离谱。
原因:匹配滤波器的卷积窗口没有对齐到信道冲激响应的起始位置。发射端加了循环前缀,接收端去循环前缀时从M//4处截断,但如果信道的首径时延恰好等于循环前缀长度附近,截断后信号的能量分布和信道响应的对齐关系被打乱,匹配滤波器在错误的时延位置做相关,积累不到信号能量。
解决:在接收端增加一个时延估计步骤,用循环前缀区域的导频或前导序列做粗同步,找到信道响应的起始点后再去循环前缀。例程里的修复方法是在wigner_transform中动态调整截断位置,依据是信道首径的采样点位置,而不是固定从M//4开始截断。这个问题的本质是理想化仿真和真实信道条件的差异,建议在跑信道模型前先打印抽头的时延分布,心里有个底。
5.3 坑三:多普勒维度过小,快速衰落信道下符号能量泄漏
现象:把仿真场景切换到高多普勒频移(比如2000Hz)后,接收端星座图完全散开,匹配滤波器输出的信噪比甚至低于输入信噪比。
原因:N=32时,OTFS符号周期T = (N×M)/FS = 0.4毫秒,多普勒分辨率是1/T=2500Hz。而信道中2000Hz的多普勒频移已经接近这个分辨率极限,导致多普勒抽头几乎落在网格边界上,能量被SFFT扩散到多个相邻多普勒索引上,匹配滤波的单点检测无法完整收集散射能量。
解决:增大N到64或128,提高多普勒域的网格分辨率。N从32增大到128后,多普勒分辨率提升到10000Hz,2000Hz的多普勒频移只占1/5个网格,能量泄漏大幅减少。代价是OTFS符号周期变长,对信道时变性更敏感,需要确保信道相干时间大于一个OTFS符号周期。这里的平衡经验是:多普勒分辨率至少要是最大多普勒频移的3到5倍。
5.4 坑四:信号峰值平均功率比过高,发射端限幅失真
现象:仿真中加入发射机功率放大器的非线性模型后,误码率曲线在低信噪比时反而比高信噪比时更差,或者星座图外圈的符号错误率明显高于内圈。
原因:OTFS信号在时域的PAPR较高,尤其是M和N都取较大的2的幂时,ISFFT的输入符号相位对齐会造成时域信号峰值叠加。非线性功放在高峰值处产生压缩失真,16QAM中幅度较大的外圈符号受损伤最严重,形成误码率与符号幅度相关的特性。
解决:在发射端加入限幅处理并调整信号功率回退。常见做法是在heisenberg_transform输出后加一个幅度限制器,把超过门限的采样点压回门限值,同时统计限幅比例确认失真程度可控。另一种思路是引入DFT扩展或在ISFFT前加相位旋转因子,把峰值打散。例程里采用了限幅加回退的组合,牺牲约0.5dB信噪比换取了功放线性度。这个坑在纯线性仿真中永远不会暴露,一旦接入真实前端或非线性模型就会立刻显现。
6. 二次开发:把例程改造成自适应调制与多用户OTFS的路径
6.1 改造一:从固定QAM到自适应调制
当前例程的调制阶数是全局固定的,实际系统中信道条件随时间变化,固定阶数要么在好信道下浪费频谱效率,要么在差信道下误码崩溃。改造思路是按帧调整调制阶数:每帧发送一小段导频,接收端估计当前信噪比,反馈给发射端决定下一帧使用QPSK、16QAM还是64QAM。
实现上需要改动的点有三个:qam_mapper里把调制阶数从全局变量改为函数参数;接收端匹配滤波后增加SNR估计模块,用导频位置的误差矢量幅度来近似;主仿真循环里加入反馈延迟的模型,让调制阶数的切换有一个帧间隔的滞后。这样改造后,例程从链路仿真升级为带有自适应机制的链路级仿真,更接近真实系统的调度行为。
6.2 改造二:从单用户扩展到多用户OTFS
多用户OTFS的资源分配方式与OFDM不同,常见方案是把延迟-多普勒网格划分为不同区域分配给不同用户。改造的关键是收发两端的网格映射函数:发射端每个用户把自己的QAM符号矩阵放置到指定的子网格中,未分配的区域置零;接收端按同样的区域划分提取每个用户的数据,再分别做匹配滤波。
例程里需要新增一个资源分配模块,定义用户索引到网格区域的映射关系,同时在接收端匹配滤波之前增加区域提取逻辑。注意多用户场景下用户间的多普勒频移可能不同,信道抽头生成需要按用户分别产生,不能复用单一用户的多径参数。从代码量上看这个改造并不大,重点在数据结构的组织方式——建议把用户信息封装成类,每个用户实例持有自己的调制阶数、网格区域和信道抽头。
6.3 改造三:对接真实信道测量数据的替换方案
如果你手里的信道探测仪输出的是抽头延时谱和时变幅度,可以直接替换channel_model.py里的随机抽头生成逻辑。把generate_channel_taps改成读取外部测量文件,解析出每条路径的时延和多普勒偏移,再映射到延迟-多普勒网格索引。需要注意测量的时延分辨率可能高于仿真网格分辨率,需要做插值或最近邻映射,否则会丢失部分路径能量。
另一种做法是保留随机信道但把信道种子固定下来,这样每次仿真结果可复现,方便对比不同接收机算法在同一批信道数据上的性能。我在调试匹配滤波和均衡算法差异时,强烈建议先固定种子再跑对比实验,否则两次实验的信道不同,误码率差异很难归因于算法本身。这套例程改到这里,已经不只是拿来跑的demo,而是一个能支撑你继续深入研究的仿真底座。希望这些改造路径能帮到你,至少让你少走一些我当年走过的弯路。
本文还有配套的精品资源,点击获取