OFDM星座图与2RC-4CPFSK仿真:从离散星座点到连续相位轨迹

发布时间:2026/10/11 22:55:00
OFDM星座图与2RC-4CPFSK仿真:从离散星座点到连续相位轨迹
简介这份OFDM仿真资源以2rc-4CPFSK信号为对象展示星座图生成与循环前缀、加窗等关键环节的MATLAB实现适合通信工程初学者理解多载波调制原理并模仿仿真流程。压缩包共12个文件包含5个.m脚本、5个.asv自动备份及1个网页说明与1个URL链接脚本覆盖OFDM演示、QAM16/QPSK解调、升余弦窗函数等模块各文件分工明确方便按需查阅便于对照代码逐步运行调试。包体仅12KB轻量精简适合快速下载使用对于课程实验或自学项目是一份便捷的入门参考。已有1026人学习浏览并持续有通信方向学习者参考借鉴。通过该资源不仅可以直接查看2rc-4CPFSK信号在OFDM系统中的星座图分布还能结合“新手必看”帮助文档梳理从信号生成、加窗、循环前缀插入到FFT解调的完整链路理解各模块与真实无线信道模拟的对应关系为后续自行扩展高阶调制或多径衰落仿真打下基础。1. 从 OFDM 星座图翻车现场看 2rc-4cpfsk 信号的正确仿真姿势OFDM 仿真的星座图是链路调试里最便宜的一张成绩单均衡没做好点是散的频偏没补偿点是转的IFFT 归一化写错点全部缩到零点几。可一旦换成 2rc-4cpfsk 信号再拿“画散点星座图”的思路去套就会直接翻车——连续相位调制的复包络不是离散星座点而是绕单位圆连续走的相位轨迹。这篇文章把两套仿真放在一起拆OFDM 星座图怎么从发送端 IFFT 一路画到接收端均衡后的散点2rc-4cpfsk 信号又该怎么从相位脉冲生成波形、画相位轨迹和相位增量。代码用 Python 加 NumPy能直接跑参数都标了为什么这样设坑按“现象、原因、解决”给齐。适合通信课程设计、物理层算法验证以及刚接手链路级仿真、不想把时间耗在玄学调参上的人。2. OFDM 星座图仿真把最小链路跑通再画散点2.1 星座图为什么能“一眼暴露链路故障”OFDM 是线性调制发射端每个有用子载波上放的都是一个个确定的复符号比如 QPSK 的四个点、16QAM 的十六个点。接收端做 FFT、均衡之后这些符号应该重新回到理想点附近。星座图就是把这些复符号的实部、虚部直接画在二维平面上理想参考点叠上去任何偏离都能肉眼看见整体旋转是残余频偏围绕理想点发散是噪声聚成几簇是 IQ 失衡点沿圆弧拖尾是符号定时偏了。所以 OFDM 的星座图不是“好看”用的它是链路体检表。调 OFDM 仿真时第一步不是看 BER而是先看星座图BER 只有一个数救不了你星座图能告诉你该往哪个模块排查。下面用一套最精简的 QPSK-OFDM 链路把流程跑通代码里故意不带信道均衡的复杂算法先把星座图这条线画直。2.2 发送端QPSK 映射、IFFT 和循环前缀怎么拼在一起先定义参数再生成频域符号。这里选了 64 点 FFT不是标准协议只是演示链路结构实际系统里 256、512、2048 都是一样的套路。import numpy as np # OFDM 参数 N_fft 64 # FFT/IFFT 点数 cp_len 16 # 循环前缀长度演示用 25% 开销 M 4 # QPSK rng np.random.default_rng(42) # 子载波分配4 个导频其余非边缘子载波当数据 pilot_idx np.array([5, 21, 43, 59]) data_idx np.array([k for k in range(1, 63) if k not in pilot_idx]) N_data len(data_idx) # 随机比特 - QPSK 符号平均功率归一到 1 bits rng.integers(0, 2, N_data * 2) data (2 * bits[0::2] - 1) 1j * (2 * bits[1::2] - 1) data data / np.sqrt(2) # ±1±j - 模长为 1 pilot np.ones(len(pilot_idx)) # 导频符号置 1 # 频域符号填入 64 个子载波 fd np.zeros(N_fft, dtypecomplex) fd[data_idx] data fd[pilot_idx] pilot # IFFT 并乘 sqrt(N)让时域平均功率和频域平均功率一致 td np.fft.ifft(fd) * np.sqrt(N_fft) td_cp np.concatenate([td[-cp_len:], td]) # 加循环前缀这段代码的关键是三个归一化。第一QPSK 映射后的符号是 ±1±j直接做平均功率是 2除以sqrt(2)后每个点模长为 1平均功率是 1。第二NumPy 的ifft自带 1/N 缩放直接输出时域功率只有频域的 1/N所以乘sqrt(N_fft)把平均功率拉回 1。第三循环前缀直接复制 IFFT 输出尾部 16 个点拼到开头这是为了把线性卷积转成循环卷积前提是多径时延小于cp_len。2.3 接收端FFT、导频 LS 均衡和星座图绘制接收端先把循环前缀丢掉再做 FFT在导频子载波上估计信道最后把均衡后的数据符号画出来。这个示例先只用 AWGN信道是平衰落导频估计更像一次“校准”但代码结构可以直接换成频率选择性信道。# 加 AWGNsnr_db 按平均符号信噪比定义 snr_db 15 signal_power np.mean(np.abs(td_cp) ** 2) noise_power signal_power / (10 ** (snr_db / 10)) noise np.sqrt(noise_power / 2) * ( rng.standard_normal(td_cp.size) 1j * rng.standard_normal(td_cp.size) ) rx_cp td_cp noise # 去 CP - FFT注意除以 sqrt(N_fft) 与发送端对应 rx rx_cp[cp_len:] fd_rx np.fft.fft(rx) / np.sqrt(N_fft) # 导频处最小二乘信道估计H_hat Y / X h_pilot fd_rx[pilot_idx] / pilot # 线性插值到全部子载波更精细可用 DFT 插值或匹配导频插值 h_all ( np.interp(np.arange(N_fft), pilot_idx.astype(float), h_pilot.real) 1j * np.interp(np.arange(N_fft), pilot_idx.astype(float), h_pilot.imag) ) # 迫零均衡再取数据子载波 rx_eq fd_rx / h_all rx_data rx_eq[data_idx] import matplotlib.pyplot as plt plt.figure(figsize(5, 5)) plt.scatter(rx_data.real, rx_data.imag, s10, alpha0.7, labelRX) plt.scatter(data.real, data.imag, markerx, colorred, labelRef) plt.axhline(0, colorgray, linewidth0.5) plt.axvline(0, colorgray, linewidth0.5) plt.axis(equal) plt.grid(alpha0.3) plt.legend() plt.show()注意np.interp对超出导频范围的子载波不往外推而是沿用端点导频值所以最边上几个数据子载波的信道估计偏“猜”演示够用实际系统要把导频放得覆盖整个信号带宽。均衡后rx_data没有再做全局幅度归一化因为发送端已经把 QPSK 平均功率设成 1。后面加 16QAM 或更高阶 QAM 时务必按星座平均功率再除一次否则画出来的点会整体偏大或偏小睁眼看起来像异常其实是忘归一。3. 2rc-4cpfsk 信号仿真不是星座点是连续相位轨迹3.1 先看 2RC 和 4CPFSK 到底是哪两个参数的组合2rc-4cpfsk 这个名字拆开是调制阶数 M4 的连续相位频移键控(CPFSK)频率脉冲采用长度为 2 个符号周期的升余弦脉冲(2RC)。CPFSK 和 OFDM 里的 QPSK 最本质的区别在于符号能量不是瞬间切到某个点上而是通过连续变化的相位把信息“抹”在波形里。CPM 基带信号可以写成s(t)exp(j*phi(t))相位是过去所有符号的累积效果。每个符号对相位的贡献由相位脉冲q(t)决定q(t)是频率脉冲g(t)的积分。对 2RC 来说g(t)只在0~2T内有值形状是升余弦q(t)从 0 平滑升到 0.5到2T之后不再变化。这个 0.5 是 CPM 的约定频率脉冲面积是 1/2而不是 1。很多第一次写 CPM 代码的人在这里栽跟头写成面积 1相位轨迹立刻变成螺旋。调制指数h也必须在仿真里显式给定。同样的 2RC-4CPFSKh0.25和h0.5画出来的相位轨迹完全不同频谱宽度和解调性能也不同。下面代码先用h0.25跑通。3.2 用相位脉冲生成 2RC-4CPFSK 基带波形生成 CPM 波形不复杂本质就是“先算每个时刻的相位再取复数指数”。我把频率脉冲和相位脉冲都实现出来方便你验证参数。import numpy as np T 1.0 # 符号周期归一化 fs 32 # 每个符号采样 32 点画轨迹够平滑 L 2 # 2RC 的脉冲宽度 2T M 4 # 4CPFSK h 0.25 # 调制指数可改成 0.5 对比 num_sym 16 # 观测符号数 rng np.random.default_rng(0) a 2 * rng.integers(0, M, num_sym) - (M - 1) # 生成 ±1, ±3 def g_2rc(t): # 2RC 频率脉冲面积 1/2 g np.zeros_like(t, dtypefloat) mask (t 0) (t 2 * T) g[mask] (1 - np.cos(np.pi * t[mask] / T)) / (4 * T) return g def q_2rc(t): # 相位脉冲 频率脉冲积分 q np.zeros_like(t, dtypefloat) rise (t 0) (t 2 * T) stable t 2 * T q[rise] t[rise] / (4 * T) - np.sin(np.pi * t[rise] / T) / (2 * np.pi) q[stable] 0.5 return q N num_sym * fs t np.arange(N) / fs phase np.zeros(N) inst_freq np.zeros(N) for i, ai in enumerate(a): phase 2 * np.pi * h * ai * q_2rc(t - i * T) inst_freq h * ai * g_2rc(t - i * T) s np.exp(1j * phase) # 恒包络复基带信号q_2rc在t2T处返回 0.5这保证每个符号在完整走完两个符号周期后对相位贡献2*pi*h*0.5*a_i pi*h*a_i。对 4CPFSK 来说a_i是 ±1、±3在h0.25时每个符号最终带来的相位增量是 ±π/4 或 ±3π/4。这个值可以作为后面自检的标尺。代码里的循环在演示规模下没问题符号数上万之后要改成“相位脉冲查表 分段累加”的写法否则 Python 循环会成为瓶颈。fs32是绘图友好的设置如果只算 BER8 到 16 点每符号通常就够具体要结合接收端匹配滤波器的实现。3.3 画 4CPFSK 的复平面轨迹、相位轨迹和符号时刻采样点OFDM 星座图画的是离散点2rc-4cpfsk 信号更该画三张图相位随时间变化、复包络在单位圆上的连续轨迹、每符号时刻的采样点位置。import matplotlib.pyplot as plt plt.figure(figsize(12, 3.5)) # 第一张相位轨迹 plt.subplot(1, 3, 1) plt.plot(t / T, phase / np.pi, linewidth1) plt.xlabel(t / T) plt.ylabel(phase / pi) plt.title(phase trajectory) # 第二张复包络轨迹 plt.subplot(1, 3, 2) plt.plot(s.real, s.imag, linewidth1) plt.axis(equal) plt.plot(np.cos(np.linspace(0, 2*np.pi, 200)), np.sin(np.linspace(0, 2*np.pi, 200)), colorgray, linewidth0.5, linestyle--) plt.title(complex trajectory) # 第三张符号时刻采样点 plt.subplot(1, 3, 3) idx_sym np.arange(num_sym) * fs plt.scatter(s[idx_sym].real, s[idx_sym].imag, cnp.arange(num_sym), cmapviridis) plt.axis(equal) plt.title(symbol spaced samples) plt.tight_layout() plt.show()两张图看完就能理解“为什么不能用 OFDM 星座图套路去套 CPM”OFDM 的散点聚集在几个理想坐标附近CPM 的复包络是一条在单位圆上连续走的曲线符号时刻采样点落在哪取决于h、L和之前的符号不是固定格点。后面要做接收机通常不看这张采样散点而是把相位轨迹送到 Viterbi 或逐符号检测里。所以给 2rc-4cpfsk 信号做仿真验证核心是“相位连续、增量正确、包络恒等”不是星座点收敛到什么位置。4. 两套仿真一起调最值得先定死的 4 类参数4.1 OFDM 侧子载波数、CP、导频密度和信噪比归一化OFDM 星座图仿真最容易出问题的不是代码写不了而是参数自相矛盾。子载波数N_fft直接决定子载波间隔带宽固定时N_fft越大子载波间隔越窄对多普勒越敏感。演示用 64 点没问题真实系统要按信道带宽和移动速度倒推不要盲目抄大点数。CP 长度要和信道时延扩展匹配。演示里cp_len16占 25% 开销偏奢侈LTE 里 2048 点 FFT 用 160 点 CP开销不到 8%。工程判断只有一条最大多径时延必须小于 CP否则前一个符号拖尾会污染当前符号星座图边缘子载波会明显分叉。导频密度取决于信道频率选择性。演示用 4 个导频插全带平信道下没问题如果信道周期短比如多径间隔大导致频域深衰落导频间隔必须小于相干带宽。另外导频复数符号建议归一化到单位模长方便 LS 估计公式里直接做除法。信噪比定义也要写进笔记里。代码里的snr_db是“整段 OFDM 符号平均功率 / 噪声功率”不是每个数据子载波的信噪比。如果数据子载波只占一半每子载波实际 SNR 要高 3dB 左右。画 EVM 和 BER 曲线时横坐标说明清楚是哪一种定义否则换个人看结果会对不上。4.2 2rc-4cpfsk 侧调制指数 h、脉冲长度 L、采样率和观测符号数2rc-4cpfsk 信号标题里没有写h所以这个参数必须自己定。常用初值我建议h0.25或h0.5。h小相位增量小频谱窄但符号间的欧氏距离小抗噪差h大抗噪好但瞬时频率偏移大频谱宽。做课程设计先从h0.25起步把链路跑通后改成 0.5 对比能直观看到频谱和相位树的差异。L2是标题里固定好的但你要知道它是“部分响应”的标志。频率脉冲长度从 1RC 变到 2RC 再变到 3RC频谱旁瓣会更低但接收端的网格状态数指数增长Viterbi 复杂度跟着上来。不看复杂度、只看波形L变大后相位轨迹更平滑这是正常现象。采样率fs是另一个容易随手乱设的参数。CPM 不是 OFDM不能用“每符号一个采样点”去压缩观测。相位脉冲在 2T 内是平滑升余弦但如果每符号只采 4 个点相位轨迹会明显折线化瞬时频率峰值可能被低估。绘图和功率谱分析建议fs32做链路级解调可以先从fs8开始验证功能。观测符号数取决于目的。画相位轨迹 16 个符号够了算功率谱至少 256 个符号还要加随机序列平均否则频谱毛刺大。总之一句话先定参数再跑波形不要边跑边把参数换来换去。4.3 参数速查表仿真对象参数推荐初值怎么判断该调OFDMN_fft64星座图边缘子载波 BER 高时查是不是保护间隔没留够OFDMcp_len16多径时延超过 CP星座图拖尾加大到信道时延 2~3 倍以上OFDM导频间隔16 子载波频域衰落深、插值后均衡发散时加密导频OFDMsnr_db15先无噪声跑通再从 20dB 往下降2RC-4CPFSKh0.25频谱太宽就调小误码平台高就调大2RC-4CPFSKfs32瞬时频率峰值过冲说明采样不足再加到 642RC-4CPFSKnum_sym16/256画轨迹用 16算功率谱用 256 以上5. OFDM 星座图与 2rc-4cpfsk 最常见的 5 类翻车现场5.1 OFDM 星座图整体旋转点数是对的角度不对现象星座图上四个 QPSK 簇清清楚楚但整体绕原点转了一个固定角度判决边界会切到错误区域BER 高得离谱。原因接收端没有补偿残余载波频偏或相位噪声。AWGN 仿真里往往看不到这条一旦加了频偏FFT 之后每个子载波会带一个公共相位旋转。解决先用导频算公共相位误差再把数据符号整体旋回去。简单做法是cpe np.angle(np.sum(fd_rx[pilot_idx] * np.conj(pilot)))然后rx_data * np.exp(-1j * cpe)。如果旋转角度随子载波序号线性变化那说明是时域频偏没消除不能只做公共相位补偿要回到时域先做 CFO 估计。5.2 星座图整体缩放漂移IFFT 归一的“0.707 陷阱”现象散点的簇很集中但位置整体比理想 QPSK 点大一号或者小一号睁眼看觉得“好像没问题”硬判决却错了一片。原因IFFT/FFT 归一化不对称。最常见的是发送端忘了乘sqrt(N_fft)接收端忘了除sqrt(N_fft)或者 QPSK 映射没除sqrt(2)平均功率变成 2星座点自然在 ±1±j 附近而不是 ±0.707±0.707j。解决写发送端和接收端时把归一化写成一对逆操作并在无噪声条件下启动自检打印时域平均功率是否等于频域平均功率。QPSK 除sqrt(2)16QAM 除sqrt(10)64QAM 除sqrt(42)。这一步做对了星座图才能从“看起来像”变成“真能判决”。5.3 2RC 频率脉冲面积写错相位轨迹从“轨迹”变成“螺旋”现象2rc-4cpfsk 信号基带波形画出来复包络轨迹不是一个稳定在单位圆上的连续曲线而是一直往外绕或者转得太猛相位值在符号区间内翻好几圈。原因频率脉冲面积被写成 1而不是 0.5。CPM 约定里完整走完一个符号后q(∞)0.5所以每个符号的最终相位贡献是π h a_i。如果脉冲面积写成 1等于把所有相位增量翻倍轨迹形态完全变形。解决用代码里给的g_2rc公式并加一句自检np.trapz(g_2rc(np.linspace(0, 2*T, 1000)), dx2*T/999)结果必须约等于 0.5。频率脉冲面积对了相位脉冲积分才是对的后面接任何解调器才可信。5.4 4CPFSK 符号映射用错相位树和接收端解调对不上现象发射端仿真看起来正常但一接解调器误码率一直卡在 50% 附近怎么调判决门限都没用。原因4CPFSK 的符号取值有两种常见约定一种是 ±1、±3另一种是 0、1、2、3。两种约定对应完全不同的相位增量如果发射端用 ±1、±3接收端模型却假设 0、1、2、3数据天然对不上。另一个隐蔽原因是收发两端的h不一致。解决在项目开头把符号集和调制指数写死成一组配置。链路调好后打印一段无噪声符号边界处的相位增量确认 2RC-4CPFSK 在h0.25时最终增量是 ±π/4、±3π/4不是这个值先回头查映射或h不要急着改解调器。5.5 CP 不够长或 FFT 窗口滑偏星座点不收敛而是拖尾现象加频率选择性信道后OFDM 星座图边缘子载波的点不向理想点收敛而是沿圆弧拖出一条尾巴中间子载波反而正常。原因循环前缀长度小于最大时延扩展FFT 窗口里混进了上一个符号的尾巴或者 FFT 窗口起点没有对准破坏了子载波正交性。导频插值太粗也会放大这个现象边缘子载波的信道估计完全失真。解决先把 CP 加长验证星座图是否恢复如果恢复说明是时延超限而不是均衡算法问题。再做定时同步可以用循环前缀相关性定位 FFT 窗口起点也可以靠前导序列粗同步。最后检查导频间隔是否小于相干带宽。这三步按顺序查能解决 90% 的拖尾型星座图问题。6. 用 EVM 给星座图打分用相位连续性和功率谱给 2rc-4cpfsk 验收OFDM 星座图不能只靠眼睛看建议每次都算一个 EVM 指标作为后续调参的基准。计算前先把整体相位对齐不然频偏带来的固定旋转会让 EVM 虚高。ref data rx_align rx_data * np.exp(-1j * np.angle(np.sum(rx_data * np.conj(ref)))) num np.sum(np.abs(rx_align - ref) ** 2) den np.sum(np.abs(ref) ** 2) evm_rms np.sqrt(num / den) evm_db 20 * np.log10(evm_rms) print(fEVM {evm_db:.2f} dB)在 15dB AWGN 下这个 EVM 应该在 -15dB 附近。如果明显差很远优先查相位补偿和归一化而不是怀疑噪声。2rc-4cpfsk 信号不适合用 EVM 验收因为它没有固定参考点。我习惯用三件事验收第一所有采样点模长都等于 1允许误差在 1e-6 量级异常说明相位计算里出现了数值漂移第二q_2rc(2*T)严格等于 0.5频率脉冲面积对第三画功率谱密度观察旁瓣下降是否平滑h变化时频谱宽度是否跟着变。功率谱可以直接用scipy.signal.welch对s做估计横轴用f*T归一化方便对比不同符号速率下的结果。我自己现在的习惯是任何新链路都先在无噪声理想信道下跑通再一档一档加 SNR。OFDM 星座图出现异常先查归一化和相位CPM 出现异常先查脉冲面积和h不要一上来就怀疑信道模型。这个流程看着土但能省下很多白天调参、晚上怀疑人生的时间。希望帮到你。本文还有配套的精品资源点击获取