☰
BFSK误码率曲线MATLAB仿真:从理论到代码全解析
2026/10/14 0:27:40 网站建设 项目流程

简介:这份MATLAB代码用于绘制二进制频移键控(BFSK)系统在不同信噪比下的误码率曲线,适合无线通信课程学习者、科研人员以及需要验证调制解调性能的工程师。代码覆盖随机二进制序列生成、AWGN信道叠加、解调、误码统计与semilogy绘图完整流程,同时代码变量命名清晰,方便修改载波频率、采样率与SNR范围,能从信号产生到性能评估完整复现,运行后直接观察BER随SNR的变化规律,也便于调整参数做拓展实验。压缩包内共有1个文件,为m格式脚本,整体仅2KB,结构简洁,无需复杂配置,适合快速学习与二次修改。目前已有1927人浏览学习。借助该脚本,既能理解BFSK频率切换与相干检测原理,也能掌握通信系统蒙特卡洛仿真、误码率计算与曲线绘制方法,还可将框架推广至M-FSK、PSK、QAM等调制方式的性能对比,适合作为教学演示与算法验证的参考。

1. BFSK误码率曲线的MATLAB代码:先从一次仿真翻车说起

如果你在搜BFSK误码率曲线的MATLAB代码,多半是被理论曲线和仿真曲线对不上这件事卡住了。明明书上写着误码率公式,自己一跑蒙特卡洛,要么整体往右偏几个dB,要么高信噪比区域乱跳。这份资源就是一个能直接改参数跑的MATLAB脚本,发射、加噪、相干解调、非相干解调、误码统计、理论曲线一条龙全串起来。适合三类人:通信课程作业要交仿真图的同学、准备面试想手撕数字调制链路的候选人、还有做项目预研懒得从头翻教材的工程师。读完之后你不仅能复现出两条经典曲线,还能知道曲线歪了该往哪个方向查。

2. BFSK信号模型与误码率理论:仿真前先立住三个坐标

2.1 信号表达式、正交条件与归一化频率间隔

BFSK做的事情很简单:用两个不同频率的载波分别表示比特0和比特1。通带形式可以写成:

s0(t) = cos(2πf1t),s1(t) = cos(2πf2t)

在复基带等效模型里,这两路信号直接写成复指数形式 exp(j2πf1t) 和 exp(j2πf2t)。很多第一次写仿真的人看到代码全是复数就发懵,其实这是故意的:复基带仿真省掉了高频载波项,只保留幅度和相对相位信息,误码率计算完全不受影响。

这里有一个决定仿真是否可信的参数:归一化频率间隔 h = Δf × Tb,其中 Δf = f2 - f1,Tb 是一个符号的持续时间。当 h 取整数时,两个频率的波形在一个符号周期内正交,相关器输出没有串扰;当 h 不是整数时,两路信号互不正交,判决时会互相干扰,理论误码率公式也不再适用。工程上最常用的最小正交间隔是 h = 1,也就是 Δf = 1/Tb。

我在代码里默认 f1 = 2000Hz、f2 = 4000Hz、Tb = 1ms,所以 Δf = 2000Hz,h = 2,满足正交条件。这个间隔比最小正交间隔大了一倍,好处是频率间隔足够远,判决余量大,曲线更贴近理论值;坏处是占用带宽更大。你拿到代码后想改小频率间隔,一定要先算一下 h 是不是整数。

频率间隔 h | 是否正交 | 理论误码率公式是否适用 h = 0.5 | 否 | 不适用,存在符号间串扰 h = 0.8 | 否 | 不适用,性能明显恶化 h = 1.0 | 是,最小正交间隔 | 适用 h = 1.5 | 否 | 不适用 h = 2.0 | 是 | 适用

2.2 相干解调与非相干解调的理论误码率公式

BFSK 有两种标准解调方式,误码率公式不同,曲线形状也不同。相干解调用本地载波做相关,因为恢复了载波相位信息,性能更好;非相干解调只比较包络大小,实现简单但性能略差。

相干 BFSK 在 AWGN 信道下的误码率是:

Pe = Q(sqrt(Eb/N0))

非相干 BFSK 的误码率是:

Pe = 0.5 × exp(-Eb / (2N0))

两个公式的适用前提是:等能量正交信号、AWGN信道、接收端做匹配滤波或等效的相关运算。如果仿真里频率间隔不满足正交条件,或者噪声功率加错了,仿真点就会和这两条理论曲线分家。

从公式能直接看出趋势:非相干比相干大约差 1dB 左右,Eb/N0 越高差距越不明显,在低信噪比区域两条曲线靠得很近。所以如果你的仿真曲线在 0dB 附近两条线几乎重合,那很正常;但如果到了 10dB 还重合,就要怀疑是不是相干解调写成了包络检测。

MATLAB 里算理论曲线,可以直接用通信工具箱的 qfunc;如果不想依赖工具箱,用 0.5 × erfc(x / sqrt(2)) 也能得到完全一样的结果。后面给出的完整代码里我写了两种写法,方便没有安装通信工具箱的环境直接跑。

2.3 为什么用复基带等效仿真而不是直接搬载波

有人会问:公式里明明是 cos(2πf1t),仿真里为什么直接写 exp(j2πf1t)?这就要说到复基带仿真和通带仿真的区别。

通带仿真要在时域把载波周期采出来。假设载频是 2GHz,符号速率 1kHz,每个符号里要采几百万个点,仿真一段误码率曲线就要跑几小时起步。复基带仿真把高频载波项去掉,只保留信号相对于某个参考频率的偏移,采样率只需要略高于信号带宽,每个符号采 8 到 16 个点就足够。

更重要的是,前面给出的误码率理论公式本身就是在等效低通模型下推导的,复基带仿真和理论公式天然对齐。代码里信号用 exp(j2πf1t) 和 exp(j2πf2t) 表示,接收端做相关时也用复指数共轭运算,整个过程和教材上的正交信号模型完全一致。理解了这一层,后面看代码里的 real 和 abs 就不会晕:real 提取的是相干相关的实部,abs 提取的是包络。

3. 从零写BFSK误码率仿真:发射、加噪、解调、统计一条龙

3.1 参数区:先把信噪比刻度和符号率定下来

写蒙特卡洛仿真第一步不是写调制,而是定参数。参数决定仿真时间、频率间隔是否正交、信噪比范围是否合理。我常用的配置如下:

% 仿真参数区 numBits = 1e5; % 每个Eb/N0下发送的比特数 nsamp = 16; % 每个符号的采样点数 bitRate = 1e3; % 比特速率 1kbps fs = bitRate * nsamp; % 采样率 16kHz f1 = 2000; % 载频1 f2 = 4000; % 载频2 deltaF = f2 - f1; % 频率间隔 2kHz Tb = 1 / bitRate; % 符号持续时间 1ms h = deltaF * Tb; % 归一化频率间隔 = 2,正交 EbN0dB = -2:1:12; % 扫描的信噪比范围 EbN0Lin = 10.^(EbN0dB / 10); % 转成线性值 rng(2024); % 固定随机种子,结果可复现

这段代码里最容易改错的是 nsamp 和 bitRate 的关系。fs = bitRate × nsamp,意思是每个符号持续时间内采 nsamp 个点。如果 nsamp 太小,比如只有 4,波形离散化误差会直接影响相关器输出;如果太大,仿真数据量成倍增加,但误码率结果不会变好。16 是个平衡值。

正常情况下整个仿真跑下来大概需要几十秒到几分钟,取决于机器和信噪比点数。后面所有调制、加噪、解调的代码都要放进for snrIdx = 1:length(EbN0dB)的循环体里,每个信噪比点独立做一次完整实验。

3.2 向量化FSK调制与复AWGN信道加噪

调制部分我推荐直接向量化,不要用 for 循环逐符号生成波形。下面的代码用矩阵外积一次性生成全部符号:

% 生成随机0/1比特流 bits = randi([0 1], 1, numBits); % 单个符号时间网格 tvec = (0:nsamp-1) / fs; % 向量化FSK调制:每个比特选一个频率 freqSeq = f1 + bits * deltaF; % 1 x numBits sigMat = exp(1j * 2 * pi * freqSeq.' * tvec); % numBits x nsamp sigTx = reshape(sigMat.', 1, []); % 转成串行样本流 % 计算符号能量并加噪 Es = sum(abs(sigTx).^2) / numBits; % 平均每符号能量 Eb = Es / 1; % BFSK每符号1bit N0 = Eb / EbN0Lin(snrIdx); % 噪声单边谱密度 noise = sqrt(N0/2) * (randn(size(sigTx)) + 1j*randn(size(sigTx))); r = sigTx + noise; % 接收信号

关键点是freqSeq.' * tvec这个外积运算。freqSeq 是 1×numBits 的行向量,转置后变成 numBits×1,再乘以行向量 tvec,就得到 numBits×nsamp 的矩阵。每一行对应一个符号的复指数波形,行内的列对应这个符号内的 16 个采样点。

reshape(sigMat.', 1, [])是为了把矩阵重新排列成一维样本流。这里必须先转置再展开,因为 MATLAB 的 reshape 是按列优先展开的,不转置的话会把第一个符号的第 1 个点、第二个符号的第 1 个点先拼到一起,信号顺序就全乱了。

噪声功率的写法sqrt(N0/2)是复基带 AWGN 的标准约定。复噪声的实部和虚部各占 N0/2 的方差,两部分合起来总功率才是 N0,这样 Eb/N0 的定义才和理论公式一致。

3.3 相干解调:相关器组的实现细节

相干解调的本质是做相关运算,把接收信号分别与本地参考波形做内积,然后比较两个相关值。MATLAB 里这步用矩阵乘法一次完成:

% 接收信号按符号切成矩阵,每一列是一个符号 rMat = reshape(r, nsamp, []); % 本地参考波形(复共轭在做内积时生效) s1 = exp(1j * 2 * pi * f1 * tvec).'; % nsamp x 1 s2 = exp(1j * 2 * pi * f2 * tvec).'; % 相干相关:取实部比较 z1c = real(s1' * rMat); % 1 x numBits z2c = real(s2' * rMat); % 判决:哪个相关值大就判哪个 bitsHatCoh = (z2c > z1c); % 统计错误比特数 errCoh = sum(bitsHatCoh ~= bits);

s1' * rMat中,s1' 是本地参考波形的共轭转置,数学上就是匹配滤波。如果发送的是比特0,接收信号近似等于 s1,那么 z1c 会得到接近符号能量的大数值,z2c 因为频率正交会接近 0,判决自然偏向 0。

为什么这里必须用real而不是abs?因为相干解调利用了完整的相位信息。取实部以后,造近信号方向上的投影是正的,正交方向上的投影接近零;如果改成 abs,实际上是把相位信息丢掉了,那就变成了包络检测,也就是非相干解调。这个细节是相干和非相干代码唯一的区别,也是最容易写错的地方。

3.4 非相干解调:包络检测与判决

非相干解调不需要恢复载波相位,实现方式是在相关器后面取包络再比较。对应到复基带代码里,就是同样的内积运算,只是把real换成abs:

% 非相干:取相关输出的包络 z1nc = abs(s1' * rMat); z2nc = abs(s2' * rMat); % 包络比较判决 bitsHatNoncoh = (z2nc > z1nc); % 统计错误比特数 errNoncoh = sum(bitsHatNoncoh ~= bits);

这段代码很短,但信息量不小。abs 在复基带等效模型里就相当于包络检波器,它把复数的幅度提取出来,丢掉相位。发送比特0时,z1nc 大致是信号能量加噪声的幅度,z2nc 则只有噪声幅度;信噪比越高,两路包络差距越大,判决越可靠。

非相干比相干实现简单,不需要本地载波同步,代价是性能差 1dB 左右。你在跑仿真时会看到两条曲线几乎平行,非相干始终在相干上方一点点,这个结果和理论公式完全吻合。

3.5 误码统计与理论曲线叠画

每个信噪比点跑完以后,把误码率存进数组,循环结束后一起画图。画图部分注意两个小细节:一是用 semilogy 画对数纵轴,二是高信噪比区域误码率可能统计到 0,直接画会丢点,最好做个下限保护。

% 循环结束后计算误码率 BERcoh(snrIdx) = errCoh / numBits; BERnoncoh(snrIdx) = errNoncoh / numBits; % 理论曲线 BERcohTheory = qfunc(sqrt(EbN0Lin)); % 相干BFSK理论误码率 BERnonTheory = 0.5 * exp(-EbN0Lin / 2); % 非相干BFSK理论误码率 % 如果没有通信工具箱,qfunc用下面这行替代 % BERcohTheory = 0.5 * erfc(sqrt(EbN0Lin / 2)); % 画图 figure; semilogy(EbN0dB, max(BERcoh, 1e-6), 'bo-', 'LineWidth', 1.5); hold on; semilogy(EbN0dB, max(BERnoncoh, 1e-6), 'rs-', 'LineWidth', 1.5); semilogy(EbN0dB, BERcohTheory, 'b--', 'LineWidth', 1.5); semilogy(EbN0dB, BERnonTheory, 'r--', 'LineWidth', 1.5); grid on; xlabel('Eb/N0 (dB)'); ylabel('误码率 BER'); legend('相干仿真', '非相干仿真', '相干理论', '非相干理论');

max(BER, 1e-6)是防止误码率为 0 时 semilogy 画不出点。如果你发现某个信噪比点下面没有曲线符号,多半就是这里在起作用;实际应用中这个点要么增加比特数重跑,要么在图上标注为“本点零误码”。

4. BFSK仿真为什么对不上理论:五个高频坑与排查顺序

4.1 现象:曲线整体向右偏移1.5到3dB,原因:复噪声方差写错

这是BFSK仿真里最经典的翻车现场。跑出来的仿真曲线形状和理论曲线完全一致,但整体往右移了大约 1.5 到 3dB,也就是仿真误码率比理论差。很多人第一反应是调制有问题,其实绝大多数情况是噪声功率加多了。

原因很简单:复基带噪声分成实部和虚部两路,每一路的方差都应该是 N0/2。如果写成noise = sqrt(N0) * (randn + 1j*randn),那么实部虚部各带 N0 的方差,合成噪声功率就是 2N0,相当于把信噪比拉低了一倍,也就是 3dB。我见过有人在这里纠错纠了一整天,最后把sqrt(N0/2)改成sqrt(N0/2)就一秒通过。排查顺序上,每跑一个新脚本先进去看加噪那行代码,再去看调制。

4.2 现象:相干曲线和非相干曲线几乎重合,原因:判决变量取了绝对值

只改一行代码,相干解调就变成了非相干解调。如果相干解调里把 z1c、z2c 用abs()计算,而不是real(),那相关性里的相位信息全部丢失,得到的其实就是包络。这样两条曲线自然重合,而且相干曲线的理论标注是错的。

解决方法是分清 use case:要模拟相干接收机,就用real(s1' * rMat);要模拟非相干接收机,就用abs(s1' * rMat)。调试时可以在某个信噪比点打印 z1c 和 z1nc 的分布,相干输出可能为负,包络输出永远大于等于零,视觉上一眼就能看出来。

4.3 现象:高信噪比区域曲线抖动剧烈甚至断点,原因:错误比特太少

当 Eb/N0 到 10dB 以上,BFSK 理论误码率已经掉到 10 的负 5 次方以下。如果你每个点只跑 1e5 比特,理论上期望错误比特还不到 1 个,统计结果自然一会是 0 一会是 2e-5,曲线看起来像锯齿。

解决方法是动态增加比特数。一个朴素准则是:每个信噪比点至少累计 50 到 100 个错误比特再停止仿真。实现上就是加一个 while 循环,每次发一批新比特,累加错误数,直到达到阈值。代价是低误码率点仿真时间会明显变长,这是正常的,没有免费午餐。

4.4 现象:仿真与理论整体对不上,改了多进制尤其明显,原因:Es/N0换算错位

BFSK 每符号只有 1 比特,Es 和 Eb 数值上相等,所以这个坑在 2FSK 里不明显。但代码一推广到 4FSK、8FSK,每符号 2 或 3 比特,如果还拿 Eb/N0 当 Es/N0 用,曲线的位置会整体错开一个 log2(M) 倍。

通用的换算是 Es = Eb × log2(M)。在仿真代码里,符号能量 Es 是按整个符号波形算出来的,而公式里的 Eb/N0 必须和比特能量对齐。我一般手动加噪而不是用awgn函数,就是因为awgn默认按输入信号平均功率算,没有显式的符号能量换算,很容易埋雷。

4.5 现象:两次运行结果不一样,原因:随机种子没固定

蒙特卡洛仿真本身有随机起伏,不固定随机种子等于没有基线。你改了一个参数,跑出新曲线和旧曲线不一样,你没法判断是参数引起的变化还是随机噪声引起的抖动。

解决方法是所有随机数生成之前固定rng(seed),每次运行结果完全一致。更进一步,我在下面第五小节会给出独立随机流的做法,让不同解调方式或不同频率间隔的对比在相同随机条件下进行,曲线差异才能真正反映信号设计本身的影响。

5. 把脚本升级成小工具:固定种子、扫频率间隔、看曲线迁移

5.1 用独立随机流做公平对比

当你需要对比相干和非相干,或者对比不同调制指数 h 的曲线时,随机抖动会干扰判断。一个实用做法是把整段仿真封装成函数,函数内部用rng(seed + snrIdx)给每个信噪比点分配一个独立但固定的随机种子。

function [BERcoh, BERnon] = simBfsk(h, numBits, seed) % h由外部指定,内部根据h设置f2频率 ... for snrIdx = 1:length(EbN0dB) rng(seed + snrIdx); % 每个SNR点独立随机流 bits = randi([0 1], 1, numBits); ... end end

这样做的好处是:同一个 h 值连续跑两次结果完全一致;不同 h 值之间因为种子不同,保证实验独立;对比两条曲线时,它们之间的差异来自频率间隔和解调方式,而不是随机数生成顺序。这是工程上非常实用的习惯。

5.2 扫描调制指数h:观察正交性如何改变误码率

把仿真封装成函数以后,就可以批量扫参数了。我扫过最多次的是 h 从 0.5 到 2 的变化,一步就能看出正交条件的分水岭:

hList = [0.5, 0.8, 1, 1.5, 2]; figure; hold on; for hidx = 1:length(hList) [~, BERnon] = simBfsk(hList(hidx), 2e5, 100); semilogy(EbN0dB, max(BERnon, 1e-6), 'o-', ... 'DisplayName', sprintf('h=%.1f', hList(hidx))); end grid on; legend;

预期会看到 h = 1 和 h = 2 的曲线完全重合,因为都满足正交条件,理论误码率相同;h = 0.8 和 h = 0.5 的曲线明显抬高,其中 h = 0.5 在高信噪比区域会有地板效应,误码率降到一定程度就下不去了。这就是非正交 FSK 代价的直接可视化。如果你想省带宽又要保性能,简单的 BFSK 做不到,得换 MSK 这类连续相位调制方案。

从那以后,我每写一套 FSK 仿真都会强制走一遍三板斧:先固定种子,再审一遍 Es/N0 和 Eb/N0 的换算,最后跑 h = 1 和 h = 2 两个正交间隔确认仿真曲线贴得住理论。这套流程帮我拦下了不少低级错误,也让我改参数改得放心。希望帮到你。

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

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

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

立即咨询