☰
MATLAB 2FSK调制解调仿真代码拆解:从信源到误码率曲线
2026/10/17 17:10:05 网站建设 项目流程

简介:这份资源是面向通信工程、电子信息类专业学生的课程设计参考资料,围绕2FSK调制与解调系统的设计与仿真展开,适合正在学习通信系统原理、需要完成MATLAB课程设计或想加深数字调制理解的学习者。压缩包内共1个doc文档,约579KB,内容涵盖设计任务与要求、方案论证、2FSK调制与解调原理、源程序代码及仿真结果分析,完整呈现了从信源产生、键控法调制、加性高斯白噪声信道到相干解调与误码率性能评估的全过程。读者可从中获取可直接参考的MATLAB7.0源程序、系统模块参数设置思路、SNR与误码率曲线绘制方法,以及相干解调与非相干解调的对比分析,便于快速搭建仿真链路并完成波形分析与性能评价。目前已有853人学习下载,对需要撰写课程设计报告或验证通信算法的人来说,是一份结构清晰、实用性较强的参考材料。

1. 从一份课设代码说起:2FSK 调制解调到底怎么跑起来

很多通信原理课设的代码,拿过来一跑就报错,或者波形出来跟书上完全对不上。我最近拆了一份基于 MATLAB 的 2FSK 调制与解调系统仿真代码,它把信源、调制、高斯信道、相干解调、非相干解调、误码率统计串成了一条完整链路,最后还画出了眼图和升余弦滤波前后的波形对比。这份代码适合正在做通信课设、想搞懂 2FSK 从比特流到误码率曲线全过程的开发者。它不依赖 Simulink 模块,纯脚本实现,参数集中、结构清晰,改几个变量就能换码速率、换信噪比、换调制方式。下面我按“能干什么 → 怎么跑 → 怎么改”的顺序,把这份代码拆开讲一遍。

2. 系统架构与信号流:从随机比特到误码率曲线

2.1 模块划分与数据流向

这份代码的架构很直白,就是通信系统教材里那张经典框图:信源 → 调制 → 信道 → 解调 → 信宿 → 性能分析。它没有用 Simulink 搭模块,而是用 MATLAB 脚本函数把每一步串起来,好处是参数全在脚本开头,改起来不用翻模块属性。

信源部分用randsrc产生 25 个二进制随机码,码速率Fd=1,系统采样频率Fs=40,载频Fc=10。调制用dmod函数,指定'fsk'方式,频率间隔df=10。信道用awgn加高斯白噪声,信噪比通过SNRpBit换算。解调分两路:相干解调用ddemod的'fsk/eye'模式,非相干用'fsk/eye/noncoh'。最后用symerr统计误码率,用simbasebandex画误码率曲线。

整个数据流是单向的,没有反馈环,所以调试起来比较直观——哪一步波形不对,就单独把那个变量拿出来画图看。

2.2 关键参数与物理含义

参数集中在脚本开头,我整理成表,方便对照修改:

参数名值含义改动影响
Fc10载波频率影响调制后波形疏密
Fs40系统采样频率必须满足奈奎斯特,太低会混叠
Fd1码速率决定码元宽度
N40每码元采样点数Fs/Fd,眼图横轴长度
df10频率间隔2FSK 两个载频之差
numSymb25仿真码元数太少误码率统计不准
SNRpBit60每比特信噪比控制噪声强度
M2进制数2FSK 固定为 2

这里有个容易翻车的地方:Fs和Fc的关系。如果Fs小于2*Fc,调制波形会混叠,画出来完全不是正弦波。我一般会先检查Fs >= 4*Fc,留够余量。

2.3 调制与信道仿真链路

调制部分用dmod一行搞定:

% 调制参数 Fc = 10; % 载频 Fs = 40; % 系统采样频率 Fd = 1; % 码速率 df = 10; % 频率间隔 M = 2; % 进制数 % 产生二进制随机码 numSymb = 25; x = randsrc(numSymb, 1, [0:M-1]); % 2FSK 调制 y = dmod(x, Fc, Fd, Fs, 'fsk', M, df);

dmod的第二个参数是载频,第三个是码速率,第四个是采样频率,第五个指定调制方式,第六个是进制数,第七个是频率间隔。这里df=10意味着两个载频分别是Fc-df/2和Fc+df/2,也就是 5 和 15。如果df太小,两个频率靠得太近,解调时带通滤波器分不开,误码率会飙升。

信道部分加高斯白噪声:

% 信噪比换算 SNRpBit = 60; SNR = SNRpBit / log2(M); % 加入高斯白噪声 seed = [12345 54321]; randn('state', seed(2)); y = awgn(y, SNR - 10*log10(0.5) - 10*log10(N), 'measured', [], 'dB');

这里SNR - 10*log10(0.5) - 10*log10(N)是把每比特信噪比换算成awgn函数需要的采样点信噪比。N是每码元采样点数,0.5是单极性信号的功率因子。这个换算公式如果写错,噪声强度会差好几个数量级,波形要么干净得不像话,要么全是毛刺。

2.4 相干与非相干解调对比

解调部分代码分两段,先相干后非相干:

% 相干解调 z1 = ddemod(y, Fc, Fd, Fs, 'fsk/eye', M, df); % 非相干解调 z2 = ddemod(y, Fc, Fd, Fs, 'fsk/eye/noncoh', M, df);

ddemod的第五个参数'fsk/eye'表示相干解调并输出眼图,'fsk/eye/noncoh'表示非相干。相干解调需要本地载波与发送载波同频同相,代码里dmod和ddemod用同一组Fc、Fd、Fs,所以相位是对齐的。如果自己改载频,两边必须同步改,否则相干解调出来的全是错码。

非相干解调不依赖相位,所以对载波相位偏差不敏感,但抗噪声性能差一些。代码里把两种解调结果都画出来对比:

% 误码率统计 [errorSym1, ratioSym1] = symerr(x, z1); [errorSym2, ratioSym2] = symerr(x, z2);

symerr返回错误符号数和误码率。在SNRpBit=60时,相干解调误码率通常为 0,非相干可能有个别错码。把SNRpBit降到 10 以下,两者差距会明显拉大。

3. 运行环境与实操步骤:从零跑通第一张波形图

3.1 MATLAB 版本与工具箱依赖

这份代码基于 MATLAB 7.0 编写,但实际在 R2015b 之后的版本也能跑,因为dmod、ddemod、awgn、symerr、rcosine、rcosflt这些函数属于 Communications Toolbox 和 Signal Processing Toolbox。如果只装了基础 MATLAB,这些函数会报未定义。

检查方法很简单,在命令行输入:

ver comm ver signal

如果输出里有 Communications Toolbox 和 Signal Processing Toolbox 的版本信息,就可以直接跑。如果没有,需要先安装对应工具箱。常见做法是装完整版 MATLAB,或者用comm开头的替代函数重写调制解调部分。

3.2 脚本分段执行与变量检查

不建议一次性全选运行,因为中间有 10 个 figure,全跑完窗口会堆满。我一般分段执行:

第一步,只跑信源和调制:

Fc=10; Fs=40; Fd=1; N=Fs/Fd; df=10; numSymb=25; M=2; x = randsrc(numSymb, 1, [0:M-1]); y = dmod(x, Fc, Fd, Fs, 'fsk', M, df);

跑完检查x是不是 0 和 1 的列向量,y的长度是不是numSymb*Fs。如果y全是 0,说明dmod参数写错了,常见是Fd和Fs位置颠倒。

第二步,加噪声并画图:

SNRpBit=60; SNR=SNRpBit/log2(M); seed=[12345 54321]; randn('state', seed(2)); y = awgn(y, SNR-10*log10(0.5)-10*log10(N), 'measured', [], 'dB'); figure(3); plot(y(1:200)); title('加入高斯白噪声后的已调信号');

看波形有没有明显的正弦疏密变化。如果噪声把信号完全淹没,把SNRpBit调大,比如 80。

第三步,解调并对比:

z1 = ddemod(y, Fc, Fd, Fs, 'fsk/eye', M, df); z2 = ddemod(y, Fc, Fd, Fs, 'fsk/eye/noncoh', M, df); [errorSym1, ratioSym1] = symerr(x, z1); [errorSym2, ratioSym2] = symerr(x, z2); disp(['相干误码率: ', num2str(ratioSym1)]); disp(['非相干误码率: ', num2str(ratioSym2)]);

如果误码率不是 0,先检查x和z1的长度是否一致。ddemod输出可能比x短几个码元,因为滤波有延迟。代码里用z1(1:numPlot)截取前 15 个对比,就是避开延迟段。

3.3 波形与眼图观察要点

眼图是判断解调质量最直观的工具。代码里画了相干和非相干解调后的眼图:

figure(4); z1 = ddemod(y, Fc, Fd, Fs, 'fsk/eye', M, df); title('相干解调后的信号的眼图');

眼图张开越大,说明码间干扰越小,判决门限越清晰。如果眼图闭合,常见原因是信道噪声太大或者滤波器带宽不对。代码后面用升余弦滤波器滤除带外噪声:

Delay=3; R=0.5; [yf, tf] = rcosine(Fd, Fs, 'fir', R, Delay); [yo2, to2] = rcosflt(y, Fd, Fs, 'filter', yf);

R=0.5是滚降系数,Delay=3是滤波器延迟。升余弦滤波后眼图会明显张开,误码率也会下降。如果R设成 0,滤波器理想但不可实现;设成 1,带宽大但频带利用率低。0.5 是常用折中。

3.4 误码率曲线绘制与 SNR 扫描

代码最后用simbasebandex画误码率曲线:

figure(8); simbasebandex([0:1:5]); title('相干解调后误码率统计');

simbasebandex是 MATLAB 自带的基带仿真示例函数,输入是 SNR 范围。它会自动跑不同信噪比下的误码率并画曲线。如果这个函数报错,常见原因是路径里没有comm示例文件夹。我一般会自己写循环代替:

SNR_vec = 0:2:16; ber_coh = zeros(size(SNR_vec)); ber_noncoh = zeros(size(SNR_vec)); for i = 1:length(SNR_vec) y_noisy = awgn(y, SNR_vec(i), 'measured'); z1 = ddemod(y_noisy, Fc, Fd, Fs, 'fsk', M, df); z2 = ddemod(y_noisy, Fc, Fd, Fs, 'fsk/noncoh', M, df); [~, ber_coh(i)] = symerr(x, z1(1:numSymb)); [~, ber_noncoh(i)] = symerr(x, z2(1:numSymb)); end semilogy(SNR_vec, ber_coh, 'b-o', SNR_vec, ber_noncoh, 'r-s'); legend('相干解调', '非相干解调'); xlabel('SNR (dB)'); ylabel('误码率'); grid on;

这样跑出来的曲线,相干解调在低信噪比下明显优于非相干,和教材结论一致。

4. 核心代码逐段拆解:调制、信道、解调、滤波

4.1 信源产生与码元映射

信源用randsrc产生随机整数:

numSymb = 25; M = 2; x = randsrc(numSymb, 1, [0:M-1]);

randsrc第一个参数是行数,第二个是列数,第三个是符号集。[0:M-1]就是[0 1],等概率产生。如果想改成非等概率,可以写成[0 1; 0.7 0.3],表示 0 出现概率 0.7。不过 2FSK 课设一般用等概率。

产生的x是列向量,长度 25。后面画图时用x(1:numPlot)取前 15 个,numPlot=15。如果numSymb小于numPlot,会报索引越界,所以改numSymb时要同步改numPlot。

4.2 2FSK 调制与频率间隔设置

调制核心是dmod:

y = dmod(x, Fc, Fd, Fs, 'fsk', M, df);

这里Fc=10是中心频率,df=10是频率间隔,所以两个载频是Fc-df/2=5和Fc+df/2=15。dmod内部用开关法产生 2FSK:当码元为 0 时输出频率 5 的正弦波,为 1 时输出频率 15 的正弦波。相邻码元之间相位不连续,因为两个频率源独立。

如果想改成相位连续的 2FSK,需要自己写积分器,不能用dmod的'fsk'模式。常见做法是用'fsk'加'cont'参数,但 MATLAB 版本不同支持不一样。我一般直接手写:

t = (0:length(x)*N-1)/Fs; phase = 2*pi*(Fc + df/2*(2*x-1)')*t; y_cont = cos(phase);

这样相位连续,但代码复杂,课设一般用开关法就够了。

4.3 高斯信道噪声功率换算

awgn函数的信噪比参数容易搞混:

SNRpBit = 60; SNR = SNRpBit / log2(M); y = awgn(y, SNR - 10*log10(0.5) - 10*log10(N), 'measured', [], 'dB');

SNRpBit是每比特信噪比,SNR是每符号信噪比。2FSK 一个符号一个比特,所以SNR = SNRpBit。10*log10(0.5)是因为单极性信号平均功率是 0.5,10*log10(N)是因为awgn按采样点算信噪比,而每个码元有 N 个采样点。

如果直接写awgn(y, SNRpBit, 'measured'),噪声功率会偏大,因为没做采样点折算。我见过有人把SNRpBit设成 60 还嫌噪声小,就是漏了10*log10(N)。

4.4 相干解调与抽样判决实现

相干解调用ddemod:

z1 = ddemod(y, Fc, Fd, Fs, 'fsk/eye', M, df);

ddemod内部流程是:带通滤波 → 与本地载波相乘 → 低通滤波 → 抽样判决。'fsk/eye'会额外画眼图。抽样判决的门限是 0.5,因为单极性信号幅度是 0 和 1。

如果想手动实现相干解调,可以拆开写:

% 带通滤波 [b1, a1] = butter(4, [(Fc-df)/Fs*2, (Fc+df)/Fs*2]); y_bp = filter(b1, a1, y); % 相干相乘 t = (0:length(y_bp)-1)/Fs; y_mix = y_bp .* cos(2*pi*Fc*t)'; % 低通滤波 [b2, a2] = butter(4, Fd/Fs*2); y_lp = filter(b2, a2, y_mix); % 抽样判决 y_sample = y_lp(1:N:end); z_manual = y_sample > 0.5;

这样每一步都可见,方便调试。但要注意滤波器延迟,y_sample可能和x不对齐,需要手动截取。

4.5 升余弦滤波与眼图生成

升余弦滤波在代码最后:

Delay = 3; R = 0.5; [yf, tf] = rcosine(Fd, Fs, 'fir', R, Delay); [yo2, to2] = rcosflt(y, Fd, Fs, 'filter', yf);

rcosine生成升余弦滤波器系数,rcosflt对信号滤波。Delay=3表示滤波器延迟 3 个码元,R=0.5是滚降系数。滤波后信号yo2比y长,因为卷积有拖尾。

眼图用eyediagram:

eyediagram(yo2, N); title('加入高斯白噪声后的已调信号的眼图');

N=40是每码元采样点数,眼图横轴显示 2 个码元。如果眼图线条很粗,说明噪声大;如果眼图闭合,说明码间干扰严重。升余弦滤波后眼图应该明显张开。

5. 避坑与常见问题:课设代码跑不通的 5 个原因

5.1 报错“Undefined function 'dmod'”

现象:运行到dmod那一行,MATLAB 提示未定义函数。

原因:没装 Communications Toolbox,或者工具箱路径没加载。

解决:命令行输入ver查看已安装工具箱。如果没有 Communications Toolbox,可以用fskmod代替dmod,但参数顺序不同。或者手写调制:

t = (0:length(x)*N-1)/Fs; freq = Fc + df/2*(2*x-1)'; y = cos(2*pi*freq.*t);

这样不依赖工具箱,但要注意freq和t的维度匹配。

5.2 波形全是直线或全是噪声

现象:figure(2)画出来是一条直线,或者figure(3)全是毛刺。

原因:Fs和Fc关系不对,或者awgn信噪比换算错误。

解决:检查Fs >= 4*Fc。如果Fs=40、Fc=10,刚好 4 倍,可以。如果Fc改成 20,Fs必须至少 80。噪声问题检查SNR - 10*log10(0.5) - 10*log10(N)这一串,少减一项噪声就会偏大。

5.3 误码率始终为 0.5

现象:symerr返回的误码率接近 0.5,说明解调输出和原信号完全不相关。

原因:相干解调时本地载波相位反了,或者ddemod的Fc和dmod不一致。

解决:确保dmod和ddemod用同一组Fc、Fd、Fs、df。如果手动改过载波,两边同步改。另外检查z1和x的长度,如果z1比x短很多,截取对齐后再算误码率。

5.4 眼图不显示或显示异常

现象:eyediagram报错或者画出来空白。

原因:输入信号长度不是N的整数倍,或者N设置错误。

解决:eyediagram要求输入长度是每码元采样点数的整数倍。yo2经过滤波后长度可能不是N的整数倍,可以截取:

yo2 = yo2(1:floor(length(yo2)/N)*N); eyediagram(yo2, N);

另外N必须等于Fs/Fd,如果Fs或Fd改了,N要同步改。

5.5 误码率曲线不下降

现象:simbasebandex画出来的曲线随信噪比增加不降反升,或者一直平着。

原因:simbasebandex是示例函数,内部用的信号和当前脚本不一致。

解决:自己写循环扫描信噪比,如 3.4 节所示。注意每次循环要重新加噪声,不能复用同一个y。另外symerr的第二个参数要截取到和x等长,否则长度不一致会报错。

6. 二次开发与参数调优:把这套代码改成你自己的课设

6.1 改调制方式:从 2FSK 到 2ASK 或 2PSK

这份代码的调制解调部分用的是dmod/ddemod的'fsk'模式。如果想改成 2ASK,把'fsk'换成'ask',df参数去掉:

y = dmod(x, Fc, Fd, Fs, 'ask', M); z1 = ddemod(y, Fc, Fd, Fs, 'ask/eye', M);

2PSK 类似,换成'psk'。但要注意 2PSK 有相位模糊问题,相干解调可能输出反相,误码率会接近 0.5。常见做法是加差分编码,或者用非相干解调。

6.2 改信道模型:从高斯信道到瑞利衰落

代码里信道是awgn,只加高斯白噪声。如果想模拟无线信道,可以加瑞利衰落:

% 瑞利衰落 h = (randn(length(y),1) + 1j*randn(length(y),1))/sqrt(2); y_fading = y .* abs(h); y_noisy = awgn(y_fading, SNR, 'measured');

这样误码率曲线会比高斯信道差很多,因为衰落会导致深度凹陷。课设如果要求“无线信道”,可以加这一段。

6.3 改性能指标:从误码率到误符号率

symerr算的是误码率,因为 2FSK 一个符号一个比特。如果改成 4FSK,M=4,一个符号两个比特,symerr算的是误符号率。要算误比特率,需要把符号转成比特再比较:

x_bit = de2bi(x, log2(M), 'left-msb'); z_bit = de2bi(z1, log2(M), 'left-msb'); [~, ber] = biterr(x_bit, z_bit);

这样统计出来的才是误比特率。课设如果要求“误码率”,一般指误比特率,要注意区分。

6.4 参数扫描与自动化跑图

手动改参数跑图效率低,我一般写个循环自动扫描:

SNR_vec = 0:2:20; ber_coh = zeros(size(SNR_vec)); ber_noncoh = zeros(size(SNR_vec)); for i = 1:length(SNR_vec) y_noisy = awgn(y, SNR_vec(i), 'measured'); z1 = ddemod(y_noisy, Fc, Fd, Fs, 'fsk', M, df); z2 = ddemod(y_noisy, Fc, Fd, Fs, 'fsk/noncoh', M, df); [~, ber_coh(i)] = symerr(x, z1(1:numSymb)); [~, ber_noncoh(i)] = symerr(x, z2(1:numSymb)); end semilogy(SNR_vec, ber_coh, 'b-o', SNR_vec, ber_noncoh, 'r-s');

这样一次跑完所有信噪比点,曲线直接出来。注意每次循环要重新加噪声,不能复用同一个y_noisy,否则噪声相关,曲线会失真。

6.5 代码结构优化:从脚本到函数

原始代码是脚本,变量全在 workspace 里,容易冲突。我一般会改成函数:

function [ber_coh, ber_noncoh] = fsk_sim(SNR_vec, numSymb) Fc=10; Fs=40; Fd=1; N=Fs/Fd; df=10; M=2; x = randsrc(numSymb, 1, [0:M-1]); y = dmod(x, Fc, Fd, Fs, 'fsk', M, df); ber_coh = zeros(size(SNR_vec)); ber_noncoh = zeros(size(SNR_vec)); for i = 1:length(SNR_vec) y_noisy = awgn(y, SNR_vec(i), 'measured'); z1 = ddemod(y_noisy, Fc, Fd, Fs, 'fsk', M, df); z2 = ddemod(y_noisy, Fc, Fd, Fs, 'fsk/noncoh', M, df); [~, ber_coh(i)] = symerr(x, z1(1:numSymb)); [~, ber_noncoh(i)] = symerr(x, z2(1:numSymb)); end end

这样调用fsk_sim(0:2:20, 1000)就能跑,变量不污染 workspace,也方便批量跑不同参数。

6.6 一个我踩过的坑:滤波器延迟导致误码率虚高

有一次我把ddemod输出的z1直接和x算误码率,结果误码率一直 0.3 下不去。后来发现ddemod内部有滤波器延迟,z1的前几个码元是滤波暂态,和x不对齐。解决办法是截取对齐:

delay = 3; % 根据滤波器阶数估计 z1_aligned = z1(delay+1:delay+numSymb); [~, ber] = symerr(x, z1_aligned);

延迟多少取决于滤波器阶数和Fs/Fd。我一般先画x和z1的对比图,肉眼找对齐点,再截取。从那以后我每次算误码率前都强制走一遍对齐检查,再也不敢直接symerr(x, z1)了。希望这份拆解能帮你把课设跑通,少走点弯路。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询