简介:《2FSK调制与解调系统设计与仿真——基于MATLAB7.0》课程设计报告,面向通信工程、电子信息类专业学生,用于完成通信原理课程设计或理解数字调制解调系统。报告完整覆盖设计任务与方案论证,包括信源产生二进制随机比特流、采用单极性矩形波、键控法生成2FSK信号、加性高斯白噪声信道、相干解调方式,并给出SNR与误码率曲线分析。同时附有MATLAB7.0源程序代码,可复现调制、加噪、相干解调及眼图绘制过程,适合对照实践。资源包共1个doc文档,整体仅579KB,文档结构清晰,包含摘要、引言、设计任务、原理说明、源程序与仿真结果等章节,便于直接参考或修改。已有853人学习下载,适合作为课程设计报告撰写、仿真实验复现及通信原理复习的实用资料。
1. 2FSK课程设计最容易被扣分的点:仿真通了,误码率却对不上理论曲线
第一次做“通讯系统原理课程设计 2FSK调制与解调系统设计与仿真”的同学,大多栽在同一个地方:波形图画得漂亮,误码率曲线却和高斯白噪声下的理论值差一到两个数量级。这个现象不是代码 bug,而是参数换算和判决逻辑的问题。这篇笔记把 2FSK 链路从发射、加噪、解调到误码率统计完整拆开,给出可直接运行的仿真脚本和参数表,适合刚学完通信原理、需要在一周内交课设报告的新手,也想让想快速搭参考链路的从业者少走弯路。
2. 2FSK系统设计先定参数:载频间隔、调制指数与带宽估算
动手写代码前,先花半小时把参数定下来,比写完再改省力得多。很多翻车现场都是随便挑了两个频率,结果频谱混叠、滤波分离不开,最后只能靠调滤波器“玄学”救场。这一章把参数选择的逻辑讲清楚,同时把相干与非相干方案的取舍也说明白,这样后面写代码时才不会被老师追问。
2.1 信号模型:把2FSK看成两个ASK叠加
2FSK 的时域表达式写成:
s(t) = m0(t)·cos(2πf1t) + m1(t)·cos(2πf2t)
其中 m0、m1 是互补的基带脉冲序列。这个写法非常适合编程:不需要在每个码元切换时维护相位,只要按码元选择载波频率即可。实际仿真中,我通常用一个随机 0/1 序列 bits,对每个码元生成对应频率的载波片段。这种“非连续相位 FSK”模型最简单,也正好匹配非相干解调的原理假设。
注意,这里生成的信号带宽会比理论上的连续相位 2FSK 稍大,因为每个码元的起始相位都重置为 0,等效于引入了相位阶跃。课设阶段一般不用管,如果后面想更接近实际,可以改成连续相位模型,在第 6 章给出实现。这里还有一个容易被忽视的点:两个载波的初相不影响非相干解调结果,但会影响信号的时域峰均比和频谱旁瓣,报告里只需要说明你采用的是哪种模型即可。
2.2 调制指数 h 怎么定:带宽与正交性的折中
2FSK 的调制指数常用 h = (f2-f1)/Rb 定义,也就是频差和码速率的比值。h 太小,两个载频距离近,接收端带通滤波器很难分离开;h 太大,占用带宽成倍增加,频带利用率变差。课设常用的区间是 h = 1 ~ 4。
当 h 为整数时,两个载波在一个码元周期内是正交的,非相干解调的理论误码率可以直接用正交 FSK 公式。h=1 时带宽最省,但两个载频只相差一个码速率,普通二阶带通滤波器的过渡带可能滤不干净。h=2 时频差是码速率的两倍,两个谱峰之间留出明显间隔,滤波器的分离难度低很多。我一般选 h=2。有些教材把调制指数写成 h = 2Δf/Rb,这时 h=2 对应的频差等于一个码速率,两种定义差一倍。写报告前先确认你用的教材定义,避免公式查错。
带宽估算常用卡森公式:B ≈ 2Δf + 2Rb。这里的 Δf 是频差的一半,即 (f2-f1)/2。按 h=2、Rb=1000 算,Δf=1000Hz,B≈2×1000+2×1000=4kHz。但实际仿真中因为相位不连续,频谱会再外扩一些,用 6kHz 作为观测带宽更稳妥。这个估算值后面画频谱图时会对应上。
2.3 一份可以直接用的参数表
以下参数是我做这类课设常用的基线配置,采样率按 100kHz 取,每个码元正好 100 个采样点:
| 参数 | 数值 | 说明 |
|---|---|---|
| Rb | 1000 bps | 码速率 |
| fc | 10 kHz | 中心频率(f1/f2 的中点) |
| Δf | 2 kHz | 频差的一半,f1=8k, f2=12k |
| h | 2 | 频差/Rb |
| fs | 100 kHz | 采样率 |
| Nsamp | 100 | 每码元采样点数 |
| 信号带宽 | 约 6 kHz | 卡森估计加相位不连续余量 |
选中心频率 10kHz 是因为它远高于码速率,满足奈奎斯特条件,又低于采样率的一半 50kHz,给滤波器留出设计余量。频差 4kHz 让两个谱峰相距明确,带通滤波器的过渡带不至于太窄。采样率 100kHz 是码速率的 100 倍,后续用滑动平均做包络检波时分辨率足够。如果你用的是 8kHz 采样率,那么 10kHz 载波就不可行了,这一点在课设答辩中经常被问到,需要提前说清楚采样定理的约束。
2.4 为什么课设常用非相干解调而不是相干解调
非相干解调不需要恢复载波相位,实现简单,频率误差容忍度也高。相干解调在理论上能比非相干解调好 1~2dB,但需要在接收端产生与发送载波同频同相的本地载波,这在仿真的黑匣子里往往被简化掉,导致很多同学假相干——以为用了乘法器就是相干,实际本地载波相位随机,性能甚至更差。
课程设计选非相干方案,主要是因为它的验证逻辑更透明:两路带通滤波器加包络检波,每一步都能用示波器或者频谱图去观察。如果选相干解调,除了误码率还得多设计一个载波同步环,工作量指数上升。我的建议是主体用非相干,在报告末尾作为进阶方向提一句相干解调的改进即可。这样既满足课设要求,又展示了独立的思考。
3. 用MATLAB脚本搭2FSK链路:发射、加噪、非相干解调与判决
这一章给出可以直接运行的脚本。我把链路拆成三段:发射端、信道、接收端,每一段都可以单独调试。如果你赶时间,也可以把三段拼到一个循环里,配误码率统计,但建议先分段验证再合并,否则出了问题很难定位。
3.1 发射端:生成随机码元与已调波形
先定义一个共享参数区,再生成 0/1 随机序列和已调信号。代码如下:
% 参数区 Rb = 1000; % 码速率 1kbps fc = 10e3; % 中心频率 df = 2e3; % 频差的一半 f1 = fc - df; % 0 码对应的载频 f2 = fc + df; % 1 码对应的载频 fs = 100e3; % 采样率 NumBits = 100000; % 参与统计的码元数 Nsamp = fs / Rb; % 每码元采样点数 rng(42); % 固定随机种子,结果可复现 bits = randi([0 1], NumBits, 1); % 按码元生成已调信号 s = zeros(NumBits * Nsamp, 1); t = (0:Nsamp-1) / fs; for k = 1:NumBits if bits(k) == 0 s((k-1)*Nsamp+1 : k*Nsamp) = cos(2*pi*f1*t).'; else s((k-1)*Nsamp+1 : k*Nsamp) = cos(2*pi*f2*t).'; end end这段代码的逻辑很直接:每个码元时长 1ms,对应 100 个采样点;bits 为 0 时填频率 f1 的余弦,为 1 时填频率 f2 的余弦。数组中第 (k-1)Nsamp+1 到 kNsamp 就是第 k 个码元对应的波形。用循环生成 100000 码元大约会让脚本慢一点,但这是最容易读懂的写法。如果你在意速度,可以把 bits 向量化生成,再配合 repmat 或 kron 拼出整段信号。
这里参数说明:f1 和 f2 之差为 4kHz,是码速率的 4 倍,所以 h=2;每个码元正好包含 f1 的 8 个完整周期或 f2 的 12 个完整周期(当 f1=8k 时 8 个,f2=12k 时 12 个)。固定 rng(42) 是为了让随机序列可复现,做实验时对比不同信噪比下的误码率必须用同一组序列,否则曲线会有抖动。如果你去掉 rng,两次运行结果会有随机差异,这会给调参带来很大麻烦。
生成后可以顺手画一下功率谱,确认两个谱峰的位置。用 pwelch(s, [], [], fs, fs) 就能看到 8kHz 和 12kHz 处有两个峰,它们之间的凹陷大约在 10kHz。这一步能快速发现频率参数写错的问题,比直接跑误码率更直观。
3.2 信道:按 Eb/N0 而不是 SNR 加噪声
很多同学在这里把信噪比和 Eb/N0 混在一起,导致横坐标和理论曲线对不上。2FSK 误码率理论公式用的是每比特能量与噪声功率谱密度之比 Eb/N0,所以加噪时也要按 Eb/N0 换算。代码如下:
EbN0_dB = 0:10; % 测试范围 ber = zeros(size(EbN0_dB)); % 保存误码率 for n = 1:length(EbN0_dB) EbN0 = 10^(EbN0_dB(n)/10); Es = mean(s.^2); % 每个符号(比特)的平均能量 N0 = Es / EbN0; % 噪声功率谱密度 noiseVar = N0 * fs / 2; % 离散噪声方差,双边谱 N0/2 noi = sqrt(noiseVar) * randn(size(s)); r = s + noi; end说明一下为什么用 Es / EbN0 得到 N0:代码里信号幅度为 1,每个码元的能量就等于信号平均功率 0.5 乘以码元时间 1/Rb,所以 Es 约为 0.5/Rb,它是一个确定的数值。mean(s.^2)统计出来和这个理论值几乎一致,直接用统计值更保险。噪声方差为什么是 N0fs/2?因为连续白噪声的双边功率谱是 N0/2,采样后带宽到 fs,折叠在 [-fs/2, fs/2] 内,积分得到总功率 N0fs/2。
这里还要说一个很容易踩的坑:如果直接用sqrt(N0) * randn(size(s))加噪声,实际噪声功率远小于应有的噪声功率,误码率会好到不真实。反过来,如果用N0*fs当方差,噪声又过大,误码率会差得离谱。一个快速自检方法是mean(noi.^2)和noiseVar是否一致,不一致就说明换算错了。
3.3 接收端:带通滤波、平方包络检波与抽样判决
非相干解调的经典结构是:两路带通滤波器分别只让 f1 或 f2 通过,然后各自做包络检波,最后比较两路包络大小。我这里用平方包络,省去 hilbert 变换的复杂度。注意滤波器要用零相位滤波,避免群延迟导致判决时刻偏移:
% 带通滤波器(二阶巴特沃斯,通带取 f1/f2 附近 ±Rb) [b1, a1] = butter(2, [(f1 - Rb) * 2/fs, (f1 + Rb) * 2/fs]); [b2, a2] = butter(2, [(f2 - Rb) * 2/fs, (f2 + Rb) * 2/fs]); % 对接收信号做零相位滤波 r1 = filtfilt(b1, a1, r); r2 = filtfilt(b2, a2, r); % 平方包络:先平方,再滑动平均(等效低通) win = ones(1, Nsamp) / Nsamp; e1 = sqrt(conv(r1.^2, win, 'same')); e2 = sqrt(conv(r2.^2, win, 'same')); % 在码元中心抽样判决 idx = round((0.5:1:NumBits-0.5) * Nsamp); det = (e1(idx) < e2(idx)); % e2大则判1,否则判0 ber(n) = sum(det ~= bits) / NumBits;重点说三个地方。第一,滤波器通带取 [f1-Rb, f1+Rb] 和 [f2-Rb, f2+Rb],单边噪声带宽约 2kHz。这个宽度既能覆盖信号能量,又不会把另一路载频收进来。第二,filtfilt是零相位滤波,输出与输入没有固定延迟;如果用filter,必须在抽样时补偿群延迟,否则误码率会明显变差。第三,平方包络用conv做 Nsamp 点滑动平均,等效于把平方后的高频分量滤除,得到包络的估计。滑动窗口宽度等于一个码元,窗口内的均值对应码元平均能量。
在上面代码里,conv(..., 'same')会造成序列两端有边界效应,但抽样点都选在码元中心,离边界至少 50 个采样点,所以不受影响。如果你用filter而不是conv,要小心初始过渡区,和解调序列的延迟叠加起来会让前几个码元不对位。
3.4 封装成误码率统计循环:完整链路一次跑通
把前面的发射、加噪、接收合并到一起,每个信噪比点跑一遍,就能得到一条完整的误码率曲线。我习惯写成函数形式,方便以后换参数:
function ber = run_fsk_sim(EbN0_dB, bits, s, fs, Rb, f1, f2) % 输入收发共用的数据和信噪比点,返回该点误码率 NumBits = length(bits); Nsamp = fs / Rb; for n = 1:length(EbN0_dB) % 这里复制 3.2 和 3.3 的加噪与解调代码 % ... end end函数封装的好处是:修改采样率、频偏、码元数量时不用到处改代码。但课设报告里不建议把函数体写得太长,老师更希望你每一步都有注释,而不是一个黑匣子。我一般把 3.1 的生成代码放在主脚本里,接收部分单独抽出函数,这样两段逻辑能分别验证。
运行时间上,NumBits=100000、11 个信噪比点,整个过程大概十几秒。如果觉得慢,把 NumBits 降到 50000,曲线毛刺会大一点但趋势不变。如果升到 500000,运行时间会接近一分钟,建议先用小规模调通,再放大跑正式数据。
到这里,一个完整的 2FSK 收发链路已经闭环。下一章把运行结果和理论值对齐,并解释为什么某些码元数下曲线毛刺特别大。
4. 仿真结果验证:把误码率曲线与理论值对齐的三个关键参数
仿真跑通只是第一步,课设报告里最重要的一张图是“误码率-信噪比曲线”。这一章解决三个问题:理论公式怎么用、仿真统计需要多少码元、滤波器带宽为什么会让曲线偏移。
4.1 理论公式要怎么用
非相干正交 2FSK 在高斯白噪声下的理论误码率是:
Pe = 0.5·exp(-Eb/(2N0))
这里的 Eb/N0 是线性值,不是 dB。画理论曲线时,先对 EbN0_dB 做 10^(dB/10) 再代入公式。要注意这个公式成立的条件是:两个载频间隔满足正交关系(h 为整数),并且接收端滤波器足够窄,带外噪声被抑制。我们选的 h=2 满足正交条件,所以理论曲线可信。
有些同学会把公式写成 Q(sqrt(Eb/N0)),那是相干 2FSK 的理论误码率。两者在高信噪比时差别不大,但从 0dB 到 10dB 区间,非相干曲线会比相干曲线高出一截。报告里如果用错公式,答辩时一眼就会被看出来。
4.2 蒙特卡洛试验次数:误码率到 10^-4 需要多少码元
误码率本身是一个随机变量,统计误差大致正比于 1/sqrt(NumBits)。比如要看到 10^-3 量级,至少需要 10^5 个码元;要看到 10^-4,至少需要 10^6 个码元,否则曲线会在理论值上下抖成锯齿。课设报告里只取 0~8dB,用 10 万码元足够展示趋势,但如果要把高信噪比段画到 10^-5,就得把 NumBits 加到 50 万甚至 100 万。
时间成本也要考虑:我写这个脚本时,100 万码元加上滤波和 conv,每个信噪比点大约要跑十几秒,全套 11 个点就是两分钟,可以接受。如果既要大码元数又怕慢,可以只在低误码率的几个点用大样本,其他点用小样本,报告里说明分点设置即可。另有一个小技巧:将同一组随机噪声分成多段并独立判决,再平均误码率,比一次性用超大样本更稳定,也更容易控制内存。
4.3 滤波器带宽对性能的影响:为什么仿真曲线总比理论差一点
理论曲线假设接收端把噪声限制在信号带宽内。实际滤波器带宽取 2Rb 时,噪声通过的不只是信号自身的 1/Rb 带宽,还有一半的富余,所以等效噪声功率比理论值大,误码率会高一些。想逼近理论值,可以把通带收窄到 [f1-0.7Rb, f1+0.7Rb] 附近,但收太窄会削掉信号边带,反而引入失真。课设阶段没必要追求完全重合,曲线趋势一致、误差在 1~2dB 内就算达标。
如果老师要求精确对齐,可以在报告里说明“实际滤波器非理想性导致约 0.5dB 损失”,这比强行把曲线画上去更诚实。还有一个验证技巧:把滤波器去掉,直接对接收信号做平方包络,误码率会高几个数量级。这能证明滤波器在链路中的关键作用,也能解释为什么有人噪声功率算错却看不出问题。
4.4 绘制理论与仿真对比曲线
画图代码通常这样写:
figure; semilogy(EbN0_dB, ber, 'o-'); hold on; % 理论曲线 EbN0_lin = 10.^(EbN0_dB/10); ber_theory = 0.5 * exp(-EbN0_lin/2); semilogy(EbN0_dB, ber_theory, '-'); xlabel('Eb/N0 (dB)'); ylabel('Bit Error Rate'); legend('仿真', '理论'); grid on;注意纵轴用对数坐标,否则低误码率部分会挤在横轴附近。如果仿真曲线在 10dB 处比理论高很多,优先检查是否漏了带通滤波,或者噪声方差换算是否用了 N0fs 而不是 N0fs/2。这两个错误几乎覆盖了 80% 的对不齐问题。
5. 避坑:2FSK仿真中常见的5个翻车场景与排查
5.1 误码率曲线在 10^-2 附近砸不下去
现象:Eb/N0 从 2dB 加到 10dB,误码率从 0.1 降到 0.01 后就不再下降,或者曲线出现平台。
原因:接收端没有加带通滤波器,宽带白噪声全进来了。平方包络检波器本身没有选频能力,两路包络都被噪声抬高,判决时输给噪声。另一个可能原因是滤波器中心频率写错,比如把 f1 写成了 fc,导致信号被滤掉大半。
解决:在包络检波前加上中心频率为 f1、f2 的两路带通滤波。滤波器通带宽度要小于两倍频差,但也不能小于信号带宽,按 2Rb 初调,再逐点试。检查滤波器传递函数的最快方法是freqz(b1,a1),看通带是否覆盖 8kHz 附近、阻带是否压制 12kHz。
5.2 仿真误码率比理论低(曲线反超理论)
现象:中低信噪比时仿真误码率比理论值还低,看起来不科学。
原因:用相干解调的结构却按非相干理论去对照。比如接收端用本地载波做乘法积分,得到一个与相位的余弦分量相关的值,这在相位完全同步时等效于相干解调,性能当然优于非相干。另一个可能原因是加噪时漏了带外噪声,相当于人为降低了噪声功率。
解决:确认接收端没有和发送载波同相的乘法器。若采用非相干方案,就必须用包络或能量判决。若报告允许相干解调,就把理论公式换成相干 FSK 的。我的经验是课设要求一般是非相干,所以不要画蛇添足。
5.3 频谱图上两个峰不对称,且带外杂散大
现象:用 FFT 画频谱,发现在 f1、f2 旁边还有一堆谐波,两个峰之间不是干净的空隙。
原因:每个码元都从载波 0 相位重新开始,相当于给连续载波加了一个方波门控,产生相位阶跃和频谱泄漏。这种泄漏是模型带来的,不是 bug。
解决:改用连续相位 FSK 实现:让载波相位在每个码元边界保持连续,即根据前一段相位累积计算当前初相,代码在第 6 章给出。但要注意连续相位模型对非相干解调的误码率几乎没有影响,所以课设主链路可以不变,只在分析频谱时对比即可。
5.4 加噪时信噪比算错,全链路噪声功率过大或过小
现象:信噪比提高,误码率反而上升;或 EbN0 设成 20dB 时误码率仍然很高。
原因:常见把N0 = Es / EbN0写成N0 = Es / EbN0 * fs,或忘记除以 2。离散噪声方差必须和采样率挂钩,正确公式是N0*fs/2。
解决:先用mean(noi.^2)检查实际噪声功率,再和理论噪声功率N0*fs/2对比。如果不相等,说明换算有误。这个自检步骤花十秒钟就能定位大部分加噪问题。另外注意,如果你把信号幅度先归一化,那么 Es 会变,噪声方差也要跟着改。
5.5 结果每次运行都不一样,报告里曲线无法复现
现象:同一份脚本,两次运行误码率曲线有波动,重跑后曲线毛刺位置不同。
原因:randi 和 randn 没有固定随机种子,每次生成的码元序列和噪声不同。蒙特卡洛仿真的随机波动被直接暴露。
解决:在脚本开头加rng(42)。更严格的做法是把随机序列存成文件,同一组序列跑完所有信噪比。这样同一台机器上任何时间重跑,结果完全一致。如果改了码元数量,重新固定种子后再跑,并记录种子值在报告里。
6. 进阶:把单路2FSK扩展成多载波与频偏容限验证
如果课设时间有余,或者你想在报告里多一个加分项,可以做一个简单的“频偏容限”实验:保持两个载频间隔不变,给接收信号附加一个频率偏移,比如 +80Hz、+200Hz,观察误码率恶化程度。实现方式是生成本地模拟的失谐载波,或者直接对接收信号的接收频率做偏移处理。我这里用最省事的办法:在发射端重新定义两个中心频率偏移后的 f1'、f2',然后仍用原滤波器和抽样位置做解调。
% 频偏容限测试:加入固定频偏 delta delta = 80; % Hz f1_drift = f1 + delta; f2_drift = f2 + delta; % 用 f1_drift 和 f2_drift 重新生成 s_drift % 接收端仍然用原 f1/f2 滤波器,统计误码率运行后你会发现,当 delta 达到频差的十分之一,即 400Hz 左右时,误码率会明显抬升。这背后是滤波器中心频率失谐,信号落在通带边缘被衰减。这个实验能直观展示非相干解调对频率同步的要求,也是报告里很容易和老师讨论的点。
另一个常用进阶是改成连续相位 2FSK,只需在发射端用一个相位累加器:
% 连续相位FSK:相位随频率变化 phase = 0; s_cp = zeros(NumBits * Nsamp, 1); for k = 1:NumBits f = f1 + (bits(k)==1) * (f2 - f1); for q = 1:Nsamp phase = phase + 2*pi*f/fs; s_cp((k-1)*Nsamp+q) = cos(phase); end end连续相位会让频谱更干净,但误码率几乎不变。我自己的习惯是先跑通非连续相位版本,确认链路无误码逻辑,再在进阶章节里对比频谱图。还可以再做一件事:改变码速率 Rb,观察误码率曲线是否保持不变。如果曲线随 Rb 明显移动,说明噪声功率换算里还藏着采样率相关的错误。
整个过程最深的教训是:不要一上来就追求高码元数和完美滤波,先固定种子、固定参数,把 0~10dB 的曲线画出来,再逐步增加复杂度。希望帮到你。
本文还有配套的精品资源,点击获取