
简介这份面向通信工程与电子信息类学生的课程设计文档围绕2FSK调制与解调系统展开重点解决MATLAB7.0环境下数字通信链路建模、相干解调实现与性能评估问题。文档按信源、调制、发送滤波、信道、接收滤波、解调、信宿等模块组织给出参数设置思路并采用开关法产生2FSK信号、加性高斯白噪声信道和相干解调方案最终通过仿真波形、眼图以及信噪比与误码率曲线分析系统性能。压缩包仅含1个doc文件大小约579KB篇幅紧凑适合作为课程设计报告模板与实验参考。已有855人学习说明其在同类课程设计中具有一定参考价值。读者可从中获取完整的系统框图、调制解调原理说明、MATLAB源程序片段与仿真结果分析便于对照搭建仿真模型、理解关键参数含义并排查波形异常与误码率曲线偏差等问题。1. 一个答辩现场的2FSK仿真跑通和讲清楚之间差着什么某高校通信原理课程设计的验收现场A同学把2FSK调制与解调系统的时域波形和频谱投到屏幕上图很干净误码率曲线也画了。老师只追问了两句调制指数取了多少、你的Eb/N0是怎么和噪声方差对上的。答不上来代码跑得再顺也证明不了信号链是通的。2FSK这门课设真正的难点不在敲代码而在四笔账参数怎么定、调制器怎么保证相位连续、解调器怎么在噪声里判对、仿真曲线怎么和理论值对上。适合刚接手课设、想一次做对的同学也适合手上有一套跑不通的旧代码、想找出问题出在哪一段的人。2. 2FSK的参数账调制指数、频偏和采样率先算清楚动手之前先把参数定死是省掉后面一大半返工的唯一办法。我见过太多人上来就写np.cos(2*np.pi*f*t)跑出来频谱一团糊然后再回头猜是频率错了还是采样率不够。这一章把2FSK设计里必须显式给出的量列清楚相位形态、调制指数、频偏、载波频率、采样率、每码元点数以及它们之间互相牵制的约束。2.1 相位不连续拼接和连续相位先决定你要哪一种2FSK的实现有两条完全不同的路子选错了后面解调、频谱、带宽分析全部对不上。第一条是相位不连续也叫相位跳变型。做法是准备两个独立振荡器频率分别是 $f_1$ 和 $f_2$来一个比特就切换一次。数学上写成$$s(t)\sum_n \cos(2\pi f_{i_n} t)\cdot \mathrm{rect}(t-nT_b),\quad i_n\in{1,2}$$关键在于每个码元内部都是 $\cos(2\pi f t)$ 从相位 0 重新起跳码元交界处相位会突变。这最贴近“两个硬件振荡器加一个开关”的物理模型也是大多数教材画原理图时的那一版。第二条是连续相位CPFSK。把比特映射成一段频率序列对频率积分得到相位$$\phi(t)2\pi\int_0^t f(\tau),d\tau,\quad s(t)\cos\phi(t)$$相位是连续的频谱滚降比跳变型快得多带外泄漏小。代价是你没法用“两路独立振荡器”去解释它仿真时也不能按码元分块独立生成。课程设计该选哪个我一般建议主报告用相位不连续版本因为它和解调端的“两路相关器/两路带通滤波器”结构一一对应讲得通进阶部分补一段连续相位对比用频谱图说明旁瓣抑制的差异这一页在报告里很值钱。两套代码都要写但花的时间差不多第二套只是换了个相位生成方式。2.2 调制指数 h 与频偏 Δf正交条件与带宽的取舍调制指数定义成 $h 2\Delta f / R_b$其中 $\Delta f|f_2-f_1|/2$ 是相对中心频率的频偏$R_b$ 是码元速率。这个量是2FSK的“玄学参数”几乎所有关于带宽、正交性、误码率的讨论都绕不开它。先说正交性。相干解调下两路信号要在一个码元内正交需要满足$$2\Delta f n R_b,\quad n1,2,3\dots$$取 $n1$ 是最小频偏也就是 $h1$。这时候两路波形在一个码元内的相关系数为零相干解调器能完全分离两路误码率最低。取 $h1$ 会出现相关性误码率上升取 $h$ 更大比如 2、3正交性依然成立但占用带宽成倍增加。再说带宽。2FSK的经验带宽估算用卡森公式$$B \approx 2\Delta f 2R_b (h2)R_b$$$h1$ 时 $B\approx 3R_b$这是性价比最高的点。有些同学为了让时域波形“看起来两个频率差得明显”直接把 $\Delta f$ 拉到 $5R_b$结果频谱占了半个奈奎斯特区后面加个带通滤波器都难做。提示课程设计报告里一定要把 $h$ 的值写出来并说明取值理由。$h1$ 对应最小正交频差是最标准的答案如果老师要求做对比就再加一组 $h0.5$非正交和 $h2$宽带正交三组误码率曲线放一张图里说服力完全不同。2.3 采样率、载波频率、每码元点数的一张参数表参数之间互相咬合最好是先定 $R_b$ 和 $f_s$再推其他。我常用的一套参数如下量级小、跑得快、频谱看得清楚参数符号取值取值依据码元速率$R_b$1000 bit/s课程设计常用量级1 ms 一个码元采样率$f_s$100 kHz每码元 100 点$f_s \ge 8f_{\max}$中心载频$f_c$10 kHz约为 $f_s/10$每载波周期 10 点频偏$\Delta f$500 Hz配合 $R_b$ 使 $h1$两个频率$f_1,f_2$9.5 kHz / 10.5 kHz$f_c \mp \Delta f$每码元点数$SPS$100$f_s/R_b$几个校验点。第一最高频率分量是 $f_2\text{旁瓣}$采样率至少要给到 $2f_2$ 以上实际取 810 倍最稳妥这里 100 kHz 对 10.5 kHz 约 9.5 倍够用。第二每码元 100 个采样点意味着一路相关器就是一个 100 点的乘加数值上非常干净也不会有累积误差。第三$f_1$ 和 $f_2$ 都在 $f_s/2$ 之下很远不存在混叠风险。如果老师给的指标是固定的比如 $R_b2400$ bit/s那就按同样比例缩放$f_s$ 取 $R_b$ 的整数倍64 或 100 倍都行$f_c$ 取 $f_s/10$ 附近$\Delta fR_b/2$。唯一不能动的是 $h1$ 这条约束它决定了 $\Delta f$ 和 $R_b$ 的绑定关系。3. 从比特流到2FSK波形Python调制的三套写法参数定了接下来把比特流变成可以直接拿去加噪声、做FFT的数组。这一章的三段代码是我反复用过的骨架比特生成、两种相位形态的调制、频谱核对。每段都能单独跑也都能拼起来。3.1 比特流与码元索引先把时间轴对齐最容易被忽略的一步是时间轴。采样点总数必须是 $N_{\text{bit}}\times SPS$多一个少一个点后面reshape就会错位误码率凭空变高还查不出来。先把常量集中定义再用整数除法算 $SPS$不要用浮点。import numpy as np RB 1000 # 码元速率 bit/s FS 100_000 # 采样率 Hz FC 10_000 # 中心载频 Hz DF 500 # 频偏 Hz SPS FS // RB # 每码元采样点数必须整除 F1, F2 FC - DF, FC DF assert FS % RB 0, 采样率必须是码元速率的整数倍 assert FS 8 * F2, 采样率相对最高频率余量不足 def gen_bits(n, seed7): # 固定种子保证每次跑出来的比特流一致便于复现和对比 return np.random.default_rng(seed).integers(0, 2, n)FS % RB 0这条断言看着多余实际能救你一旦有人把 $R_b$ 改成 2400 而 $f_s$ 还是 100 kHz整除立刻失败你能第一时间发现而不是等到误码率对不上再去猜。gen_bits里固定种子是为了让“同一组比特、不同信噪比”的对比成立扫描误码率时尤其重要——换种子会让两条曲线抖动得看不出趋势。3.2 相位不连续实现两个振荡器按位切换按位循环每来一个比特就选一个频率码元内部从相位 0 重新起跳。这种写法和硬件框图一一对应讲课设时最好讲。def fsk_discontinuous(bits, fsFS, f1F1, f2F2, spsSPS): n len(bits) out np.empty(n * sps) t np.arange(sps) / fs # 一个码元内的时间轴长度 sps for i, b in enumerate(bits): f f2 if b 1 else f1 # 每个码元都从相位 0 起跳这就是相位不连续的来源 out[i * sps:(i 1) * sps] np.cos(2 * np.pi * f * t) return outt只算一次并复用避免在循环里重复构造数组。out用np.empty预分配长度严格等于 $n\times sps$。循环体里没有累加相位所以码元边界一定跳变——这不是bug是特性频谱图上的旁瓣就是这么来的。顺带说个血泪经验如果写成np.cos(2*np.pi*f*np.arange(n*sps)/fs)一次性生成虽然更快但那是“两个频率各自铺满全时长再拼接”的写法边界相位同样跳变效果一样只是可读性差、不好按码元做门控。我建议保留按位循环的版本报告里贴代码也更好讲。3.3 连续相位实现频率序列做相位累加连续相位版本的核心是把频率展开成与采样点等长的序列然后做累积求和得到相位。这里没有任何按码元分块的操作整条波形是一个整体。def fsk_continuous(bits, fsFS, f1F1, f2F2, spsSPS): # 每个比特复制 sps 份得到逐采样点的瞬时频率 freq np.repeat(np.where(bits 1, f2, f1), sps) # 相位是频率的积分phi[k] 2*pi*sum(freq[0..k])/fs phase 2 * np.pi * np.cumsum(freq) / fs return np.cos(phase) def fsk_continuous_baseband(bits, f1F1, f2F2, spsSPS): # 复基带版本只保留相位去掉载波后面扫BER时直接用 freq np.repeat(np.where(bits 1, f2 - FC, f1 - FC), sps) phase 2 * np.pi * np.cumsum(freq) / SPS # 归一化到码元速率 return np.exp(1j * phase)np.cumsum(freq)/fs这一步就是在做数值积分离散形式是 $\phi[k]\phi[k-1]2\pi f[k]/f_s$cumsum恰好等价。要注意freq里存的是绝对频率累积出来的相位绝对值很大几万弧度但np.cos只关心相对值浮点精度在双精度下完全够不用担心。第二个函数是给后面第 6 章准备的把载波去掉只留 ±$\Delta f$ 的频偏这样仿真步长可以放大、点数可以减到几千扫 10 个信噪比点几秒钟就跑完。带通仿真和基带等效仿真的误码率应该重合这本身就是一次很好的验证。3.4 FFT核对两个峰必须落在 f1 和 f2波形生成完别急着往下走先做一次频谱核对。这一步花 30 秒能提前发现 80% 的参数错误。import matplotlib.pyplot as plt def spectrum_db(x, fsFS): w np.hanning(len(x)) # 加窗压旁瓣避免强信号盖住弱分量 X np.fft.rfft(x * w) f np.fft.rfftfreq(len(x), 1 / fs) mag 20 * np.log10(np.abs(X) / np.max(np.abs(X)) 1e-12) return f, mag bits gen_bits(200) x_nc fsk_discontinuous(bits) x_cp fsk_continuous(bits) f, m1 spectrum_db(x_nc) _, m2 spectrum_db(x_cp) plt.plot(f, m1, labelphase-discontinuous) plt.plot(f, m2, labelCPFSK) plt.xlim(0, 25_000); plt.ylim(-60, 3) plt.xlabel(Hz); plt.ylabel(dB); plt.legend() plt.show()正确的结果是主瓣出现在 9.5 kHz 和 10.5 kHz 附近不连续版本在两侧还有明显的旁瓣拖着连续相位版本滚降快至少十几 dB。如果峰跑到 5 kHz 或 20 kHz先查F1/F2有没有被后面的代码覆盖如果只有一个峰多半是比特流全是 0 或全是 1gen_bits的种子给错了如果峰很宽糊成一片那就是采样率不够或者点数太少导致频率分辨率不足。rfftfreq(len(x), 1/fs)的分辨率是 $f_s/N$200 个比特 20000 点分辨率 5 Hz分辨 1 kHz 的频差绰绰有余。做报告图时把点数提到 2000 比特以上频谱会更干净。4. 解调端相干、非相干和过零检测的代码与边界调制出来的波形谁都会写真正拉开差距的是解调。三种做法在课设里都会被提到但它们的适用条件和代码结构差别很大混着写就会得到一个“看起来能跑、换个信噪比就崩”的解调器。4.1 相干相关器结构最简单前提最苛刻相干解调的前提是收发端载波同频同相。课程设计里我们一般直接假设这个条件成立不做载波恢复只验证“给定理想同步误码率能不能达到理论值”。结构上就是两个相关器把接收信号分别与 $\cos(2\pi f_1 t)$、$\cos(2\pi f_2 t)$ 相乘并在一个码元内积分比大小。def demod_coherent(rx, fsFS, f1F1, f2F2, spsSPS): n len(rx) // sps blocks rx[:n * sps].reshape(n, sps) # 每个码元一行 t np.arange(sps) / fs c1 np.cos(2 * np.pi * f1 * t) c2 np.cos(2 * np.pi * f2 * t) y1 blocks c1 # 相关器乘加等价于积分 y2 blocks c2 return (y1 y2).astype(int) # y2 大判 1否则判 0blocks c1用的是矩阵乘等价于对每一行做内积也就是“相乘后求和”。这一步同时完成了混频、低通和抽样三个动作比真的去调scipy.signal.butter再滤波干净得多也没有群时延问题。n len(rx)//sps保证 reshape 不会报错最后不足一个码元的尾巴直接丢掉。判决策略是y1 y2时判 1。注意这是相对比较不是拿某个绝对门限去卡所以信号整体放大缩小不影响结果。这一点在第 5 章讲门限坑的时候还会回来。4.2 非相干正交下变频取包络工程上最常用相干解调要求相位对齐实际系统里很难做到。非相干解调只看包络对相位不敏感代价是同样的信噪比下误码率略高大约差 1 dB。实现方式是用复指数做下变频再取模def demod_noncoherent(rx, fsFS, f1F1, f2F2, spsSPS): n len(rx) // sps blocks rx[:n * sps].reshape(n, sps) t np.arange(sps) / fs e1 np.exp(-2j * np.pi * f1 * t) e2 np.exp(-2j * np.pi * f2 * t) y1 np.abs(blocks e1) # 取模 正交两路包络合成 y2 np.abs(blocks e2) return (y1 y2).astype(int)np.exp(-2j*pi*f*t)是复本振实部对应 I 路、虚部对应 Q 路blocks e得到的是一个复数取np.abs就是两路平方和开根号正是包络检波的结果。和 4.1 的代码相比只多了一个abs和复指数但鲁棒性完全不是一个量级给接收信号乘任意相位因子 $e^{j\theta}$4.1 的结果会乱4.2 的结果纹丝不动。这个性质在第 6 章的相位旋转实验里会被用来做对照也是报告里“为什么选非相干”的有力论据。4.3 过零检测能过答辩但只在干净信道里成立过零检测是老教材里必提的一种方法2FSK 两个频率对应不同的过零密度数一个码元内过零次数就能判决。实现起来只有三行def demod_zero_cross(rx, spsSPS, thresh20): n len(rx) // sps blocks rx[:n * sps].reshape(n, sps) # signbit 转 0/1相邻差分绝对值求和就是过零次数 zc np.sum(np.abs(np.diff(np.signbit(blocks).astype(int), axis1)), axis1) return (zc thresh).astype(int)门限 20 是这么来的$f_19.5$ kHz码元 1 ms约 9.5 个周期双向过零约 19 次$f_210.5$ kHz 约 21 次。取中间值 20 最合理。但这个方法非常脆弱。加一点点噪声过零点就会在真实零点附近来回抖一次抖动就是两次额外的过零计数判错是必然的。我实测下来信噪比低于 15 dB 之后误码率就奔着 10⁻¹ 去了而且它不是平滑恶化是断崖式失效。所以我的建议是过零检测只用来做原理演示报告里写清楚它的适用条件是高信噪比、无滤波、无多径然后主体误码率曲线用 4.1 或 4.2 的结果。把这条边界写明白反而是加分项。4.4 判决门限和抽样时刻两个必须显式写出来的量上面三段代码有个共同点抽样时刻都取在码元块的末尾因为reshape之后整块做完相关就出结果相当于在码元内做了完整积分。这在理想情况下没问题但如果中途加了滤波器输出会整体后移抽样点就落在了码元边缘甚至下一个码元上误码率立刻抬高一个数量级。解决办法是显式引入抽样偏移def sample_with_offset(y, offset0): # y: 每个码元的判决量offset: 向后偏移的码元数可为小数 idx np.arange(len(y)) int(round(offset)) idx np.clip(idx, 0, len(y) - 1) return y[idx]更稳妥的做法是在相关器之前就对blocks做滑动窗口让窗口中心对齐码元中心。课程设计里如果不加滤波器直接用整块相关就够了一旦加了带通或低通就必须在报告里写一句“已补偿滤波器群时延 $\tau_g$抽样点后移 $\tau_g f_s$ 个采样点”。这句话写出来说明你知道问题在哪。判决门限同理。别用固定值 0.5 去卡y1要用y1和y2的相对大小或者用y1 - y2的符号。前者对幅度变化免疫后者还顺带把直流偏置抵消了。这个习惯从课设带到工程里都不会错。5. 五个真实翻车现场2FSK仿真避坑与排查以下五条是我在帮人看课设代码时反复遇到的基本覆盖了 2FSK 仿真里 90% 的“跑不通”。每条按现象、原因、解决来写对着查就行。5.1 误码率曲线卡在 1e-2 不再下降现象Eb/N0 从 0 dB 扫到 10 dB误码率一路下降到 8 dB 附近就趴在 1e-2 上下不动了再往上加信噪比也没用曲线是一条水平线。原因这是典型的误码平台八成是抽样时刻偏了。加噪声前波形是对的加噪声后解调器里如果用了scipy.signal.lfilter做带通或低通滤波器的群时延会把整个信号推后几十个采样点而你还在按原来的码元边界切块等于每个码元都取到了相邻码元的尾巴。信噪比越高这个错位造成的错误占比越大所以曲线会收敛到一个常数。解决先不加滤波器用 4.1 或 4.2 的相关器直接跑一遍看曲线能不能贴到理论值。如果能说明问题确实在滤波器那就查滤波器的群时延butter设计的 FIR 用filtfilt做零相位滤波IIR 就老老实实算出(N-1)/2的延迟并对blocks做偏移。还有一个更省事的办法把滤波器的通带做宽一点或者干脆改用相关器因为相关器本身就是最优匹配滤波器根本不需要额外滤波。5.2 频谱上只有一个峰或者峰的位置对不上现象FFT 出来只有一个明显的峰或者两个峰挤在 10 kHz 附近分不开跟参数表上写的 9.5/10.5 kHz 对不上。原因三种可能。一是比特流退化全是 0 或者全是 1那就只有一个频率二是F1、F2在别处被重新赋值了比如后面为了“让波形好看”改成了 1000/2000 Hz而绘图时还按原参数标注三是 FFT 点数太少频率分辨率 $f_s/N$ 大于 1 kHz两个峰糊成一个。解决先print(np.unique(bits, return_countsTrue))确认比特流是均衡的再print(F1, F2)确认常量没被覆盖最后把len(x)提到 20000 点以上。还有一个隐蔽的坑画频谱时如果没加窗强信号比如直流分量或者载波泄漏的旁瓣会盖住另一路看起来像只有一个峰——np.hanning那一行别省。5.3 相干解调一加信道就崩现象不加噪声时相干解调误码率为 0加一点点噪声之后误码率直接飙到 0.3 以上比随机猜还差。原因相干解调假设收发载波同相。你的调制端用np.cos(2*np.pi*f*t)解调端也用同一个表达式看起来是同步的。但只要中间经过了任何带相位响应的环节滤波器、延迟、重采样或者你为了“更真实”给信号乘了一个 $e^{j\theta}$相位就偏了。相位偏 180° 时判决完全反过来误码率接近 1表现出来就是“比随机猜还差”。解决两个方向。要么老老实实做载波恢复科斯塔斯环或者判决反馈课设里工作量大一般不做要么就用非相干解调并且把“相干解调需要理想同步”这句话写进报告的假设条件里。我一般会在报告里加一张对比图理想相干、非相干、相位偏 30° 的相干三条线放一起一眼就能看出相干解调对相位有多敏感。这张图比写十行文字都管用。5.4 过零检测在低信噪比下彻底失效现象高信噪比时过零检测误码率为 0信噪比一降到 15 dB 以下误码率突然跳到 0.2 以上而且再怎么调门限都救不回来。原因这不是实现问题是原理性的。过零检测本质上是在数频率噪声会让信号在真实过零点附近反复穿越零轴一次抖动贡献两个额外计数。码元内过零次数从 19/21 变成 25/17 这种离谱值判决必错。而且这个误差不是高斯的不会随信噪比平滑下降。解决接受它别硬救。报告里把过零检测定位成“高信噪比下的原理验证”误码率曲线只画到 15 dB然后明确写“低信噪比下应采用相干或非相干相关解调”。如果老师要求必须有一条完整的过零检测曲线那就在检测前加一级窄带带通滤波把噪声压下去曲线能好看一些但代价是引入了群时延回到 5.1 那个坑。5.5 加了滤波器之后判决点整体后移现象单独测滤波器输出是对的波形完整、没有失真但一接上解调器误码率就高把判决点往后挪 2030 个采样点就正常了。原因因果滤波器的群时延。scipy.signal.butter(4, ...)设计出来的 4 阶巴特沃斯群时延大约几个到十几个采样点阶数越高延迟越大。你按blocks rx.reshape(n, sps)切块切到的是延迟后的波形码元边界全错位了。解决三个选项按推荐顺序排。第一用scipy.signal.filtfilt做零相位滤波前后各滤一遍相位响应抵消延迟为零代价是需要整段数据不能流式处理——课设场景完全够用。第二手动补偿delay (N-1)//2切块时rx[delay:]。第三也是最省事的压根不用滤波器相关器自带带通特性匹配滤波就是最优的。6. 把曲线推到能写进报告基带等效扫BER和相位鲁棒性验证最后一章讲两件让报告从“能跑”变成“有说服力”的事用基带等效快速扫出误码率曲线并与理论值对照以及用相位旋转实验定量说明相干解调的脆弱边界。6.1 基带等效省掉载波扫一遍Eb/N0带通仿真每码元 100 点扫 10 个信噪比、每个信噪比 20000 比特跑下来要几十秒。把载波去掉、只保留 ±500 Hz 的频偏每码元 8 点就够速度快一个数量级结果完全一致。from scipy.special import erfc import numpy as np def qfunc(x): return 0.5 * erfc(x / np.sqrt(2)) def ber_2fsk(ebn0_db_list, nbits20000, sps8, seed1): rng np.random.default_rng(seed) res [] for db in ebn0_db_list: bits rng.integers(0, 2, nbits) # 基带等效只保留 /-df 的相位旋转 df_norm 0.5 # h1 时 df*Tb 0.5 freq np.repeat(np.where(bits 1, df_norm, -df_norm), sps) phase 2 * np.pi * np.cumsum(freq) / sps tx np.exp(1j * phase) # 每比特能量归一化为 1噪声方差 1/(2*Eb/N0) 每维 ebn0 10 ** (db / 10) sigma np.sqrt(1 / (2 * ebn0)) rx tx sigma * (rng.normal(sizelen(tx)) 1j * rng.normal(sizelen(tx))) # 非相干解调按码元取模比较 blk rx.reshape(nbits, sps) t np.arange(sps) / sps y1 np.abs(blk np.exp(-2j * np.pi * (-df_norm) * t)) y2 np.abs(blk np.exp(-2j * np.pi * (df_norm) * t)) ber_sim np.mean((y1 y2).astype(int) ! bits) res.append((db, ber_sim)) return res关键在噪声方差每比特能量归一化成 1 之后复高斯噪声每一维的方差是 $1/(2\cdot E_b/N_0)$两维加起来正好是 $1/(E_b/N_0)$信噪比才对得上。这一步错了整条曲线会整体平移好几 dB而且看不出来。理论值方面相干 2FSK 是 $P_bQ(\sqrt{E_b/N_0})$非相干 2FSK 是 $P_b\tfrac12 e^{-E_b/(2N_0)}$。把仿真点和这两条理论线画在一张半对数图上相干曲线应该贴着 $Q$ 函数非相干曲线在相同 $E_b/N_0$ 下高出约 1 dB这是教科书结论对上了就说明整条链路是通的。6.2 相位旋转实验相干解调的脆弱点在哪第二个实验更直观给接收信号乘一个相位因子 $e^{j\theta}$$\theta$ 从 0 扫到 $\pi$看相干解调和非相干解调的误码率怎么变。def phase_sweep_demo(theta_list, ebn0_db8, nbits20000, sps8, seed2): rng np.random.default_rng(seed) out [] df_norm, ebn0 0.5, 10 ** (ebn0_db / 10) bits rng.integers(0, 2, nbits) freq np.repeat(np.where(bits 1, df_norm, -df_norm), sps) tx np.exp(2j * np.pi * np.cumsum(freq) / sps) sigma np.sqrt(1 / (2 * ebn0)) rx0 tx sigma * (rng.normal(sizelen(tx)) 1j * rng.normal(sizelen(tx))) for th in theta_list: rx rx0 * np.exp(1j * th) blk rx.reshape(nbits, sps) t np.arange(sps) / sps # 相干取实部对相位敏感 c1 np.real(blk np.exp(-2j * np.pi * (-df_norm) * t)) c2 np.real(blk np.exp(-2j * np.pi * (df_norm) * t)) ber_coh np.mean((c1 c2).astype(int) ! bits) # 非相干取模对相位免疫 y1 np.abs(blk np.exp(-2j * np.pi * (-df_norm) * t)) y2 np.abs(blk np.exp(-2j * np.pi * (df_norm) * t)) ber_nc np.mean((y1 y2).astype(int) ! bits) out.append((th, ber_coh, ber_nc)) return out跑出来的形状很有意思非相干误码率在整条 $\theta$ 轴上几乎是一条平线相干误码率在 $\theta0$ 附近最低到 $\pi/2$ 附近恶化到 0.5然后继续变差到 $\pi$ 附近反过来变成“几乎全错”。这条曲线放在报告里比任何文字说明都有力——它直接告诉你为什么实际系统不用理想相干解调也告诉你为什么“相干解调假设收发载波同相”这句话必须写在假设条件里。我自己踩过最深的一个坑是刚开始做这个课设时把相干解调的相关器写成了np.real(blk np.exp(-2j*pi*f*t))却忘了相位跑出来误码率一直是 0.5查了整整一晚上最后发现调制端用的是np.cos实信号解调端用复指数下变频后取了实部两边的相位基准差了 90°。从那以后我养成了一个习惯任何解调器的第一版先不加噪声用单频正弦喂进去看判决量是不是干净的两个电平。这一步花五分钟能省掉后面所有的猜谜时间。希望帮到你。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。