简介:面向通信工程与信号处理学习者的小型示例包,演示如何在MATLAB/Simulink环境中搭建AWGN(加性高斯白噪声)信道仿真模型,用于分析不同信噪比下信号的失真程度与误码率变化。压缩包共3个文件,核心为Simulink模型文件(.mdl)、负责参数设置与仿真实例的MATLAB脚本(.m)以及说明文档(.txt),整体大小仅4KB,属于轻量级入门示例。从脚本和模型可以看出,内容涵盖信号源、AWGN信道、接收滤波及误码率统计等关键环节,适合对照学习AWGN信道的基本仿真流程、SNR参数配置和结果分析方法。目前已有274人学习下载,对刚接触Simulink通信仿真的读者具有较好的参考价值。
1. 从 AWGN.rar 看起:Simulink 信道仿真到底在仿什么
如果你手头有一个名为 AWGN.rar 的压缩包,解压后大概率是几层嵌套的文件夹,里面躺着awgn_simu.mdl或.slx模型,再加几个魔改过的初始化脚本。这种包在通信课程设计和工程预研里非常常见,但真正能一次跑通的人不多。原因有两层:一是 Simulink 里AWGN Channel模块的参数名和 MATLAB 的awgn()函数不完全一致,很多人查了半天资料还对不上;二是仿真结果出来后,BER 曲线和理论值对不上,不知道是信噪比换算错了,还是信号延迟没对齐。
AWGN(Additive White Gaussian Noise,加性高斯白噪声)是通信仿真里最基础也最绕不开的信道模型。所谓“白”指功率谱密度平坦,“高斯”指幅度分布服从正态分布,“加性”就是直接叠加在信号上。它解决的核心问题很简单:给定一个调制信号,在特定信噪比下,误码率到底是多少。这个标题背后的实际需求,往往是用 Simulink 搭一个可视化链路,再用 MATLAB 脚本批量扫点画 BER 曲线。适合的读者是刚接触通信仿真的研究生、做物理层预研的工程师,以及想验证自己编解码算法的人。接下来我按“搭模型、调参数、复现理论、排查问题”的顺序,把这条链路完整走一遍。
2. Simulink 里搭 AWGN 仿真链路:从模块布局到符号参数
2.1 最小模型结构:发端、信道、收端三件套
在 Simulink 里做 AWGN 信道仿真,最常用的模型拓扑是:伯努利二进制源 → BPSK 调制 → AWGN 信道 → 判决解调 → 误码率统计。模型用不着加均衡器或者同步器,因为 AWGN 既不引入频率偏移也不引入符号间干扰,链路是内存无记忆的。打开 Simulink 库浏览器,依次拖入以下模块:
Bernoulli Binary Generator(Communications Toolbox):生成 0/1 序列,默认概率 0.5,输出帧格式。BPSK Modulator Baseband:把 0/1 映射为 +1/-1。AWGN Channel:叠加高斯噪声,注意它属于 Communications Toolbox,不在基本 Simulink 库。BPSK Demodulator Baseband:硬判决,输出 0/1。Error Rate Calculation:统计误码数、误码率和总比较符号数。
连接方式很简单:源到调制器,调制器输出进 AWGN 信道,信道输出同时进入解调器和误码率模块的信号端。关键点是Error Rate Calculation模块有两个输入:Tx和Rx。Tx接延迟对齐后的发送端比特(不是调制前的,也不是调制后的),Rx接解调输出。很多人直接接调制器输出和解调器输出,那统计的是星座点符号错误率,虽然比例上有对应关系,但和通常说的 BER 不是一回事。
2.2 参数设置的四个必调项
用鼠标双击 AWGN Channel 模块,会看到 “Parameters” 页签。最容易被坑的是 “Mode” 下拉框默认是Signal to noise ratio (Eb/No),而不是你脑子里默认的SNR (dB)。两个模式都要求填“每个符号的比特数”或“符号周期”,如果忘了填,模块会按默认值抛错或悄悄改变噪声功率。下面表格给出常用模式与填法:
| Mode | 需要填的参数 | 输入含义 | 典型场景 |
|---|---|---|---|
| Signal to noise ratio (Eb/No) | Eb/N0 (dB)、Number of bits per symbol | 每比特能量与噪声功率谱密度之比 | BPSK 理论 BER 对比 |
| Signal to noise ratio (Es/No) | Es/N0 (dB)、Input signal power | 每符号能量与噪声谱密度之比 | 多进制调制常用 |
| Signal to noise ratio (SNR) | SNR (dB)、Signal power、Symbol period | 信号功率与噪声功率之比 | 直接对接示波器读数 |
以 BPSK 为例,Number of bits per symbol填 1,Eb/N0 和 SNR 是同一个值。但如果你用的是 16QAM,这一项填 4,要不要做log2(4)的换算就取决于你的习惯。我一般直接在 MATLAB 脚本里计算好 Es/N0 再填入,因为 Simulink 模型参数化之后批量扫点更方便。
另外两个必调项是Input signal power和Symbol period。Input signal power默认是1(瓦),这要求前置调制器输出的信号平均功率为 1。BPSK 的 +1/-1 星座点平均功率正好是 1,所以通常不用改。但如果前面接了成形滤波器或者放大模块,信号功率会变,填错 1 会造成实际信噪比比设置值低 3dB 这种隐蔽错误。
2.3 用脚本批量扫描信噪比的正确姿势
手动在模块界面改 Eb/N0 再点运行,只适合验证单点。要画 BER 曲线,得把模型参数化。我常用的做法是:在模型里把 AWGN 模块的 Eb/N0 框填成EbN0_dB,这是一个由工作区注入的变量,然后在 MATLAB 脚本里用sim()函数循环调用。
%% 批量扫描 Eb/N0 并收集 BER EbN0_list = 0:2:10; % 扫 0 到 10 dB,步进 2 ber_results = zeros(size(EbN0_list)); % 预分配 for idx = 1:length(EbN0_list) EbN0_dB = EbN0_list(idx); % 写入工作区变量 % 设置仿真停止时间为 1e5 个符号(BPSK 即 1e5 比特) simOut = sim('awgn_simu', 'StopTime', '1e5'); % 模型里的 Error Rate Calculation 模块可以用 simout 输出 ber_results(idx) = simOut.ber(1); % ber 信号是 [误码率, 误码数, 比较数] fprintf('Eb/N0 = %.2f dB, BER = %.5f\n', EbN0_dB, ber_results(idx)); end % 画图并与理论值对比 semilogy(EbN0_list, ber_results, 'bo-'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER');这段脚本里需要注意:simOut.ber(1)的取法是靠 Error Rate Calculation 模块的Output data勾选为Port,并把输出端口接到simout模块上。如果你只是用sim()而没配置输出,结果是空的。另一个常用参数是StopTime,这里写成字符串'1e5'表示仿真 10 万个符号。BPSK 下误码率在1e-4量级时,至少要仿真 10^6 个符号才有统计意义,否则 BER 曲线会是毛刺状。所以实际跑曲线时,高信噪比段的 StopTime 要拉长,低信噪比段可以短一点。
还有一个细节:sim()的返回对象在 R2016a 之后是Simulink.SimulationOutput,要取信号时用simOut.ber,而不是simOut.get('ber')。如果你用的是旧版本,写成后者也行,但新版本里两种写法都兼容。脚本里用fprintf打印进度,是为了在批量跑长仿真时能随时看到跑到哪里,总比干等强。
3. 用 MATLAB 脚本还原 AWGN 模型:等价结构与参数换算
3.1awgn()函数和手动加噪的区别
Simulink 里的 AWGN 模块本质上就是对调制信号加上功率受控的高斯噪声。在 MATLAB 纯代码里,最省事的是直接调用awgn()函数:
%% BPSK 仿真的 MATLAB 代码版 M = 2; % BPSK EbN0_dB = 6; % 目标 Eb/N0 numBits = 1e6; % 仿真比特数 data = randi([0 1], numBits, 1); % 随机比特 sym = 2*data - 1; % BPSK 映射:0->-1, 1->+1 % 调用 awgn 函数,注意信号是实数,'measured' 会按实测功率施加噪声 rx = awgn(sym, EbN0_dB, 'measured'); % 硬判决 rxBit = rx > 0; % 统计误码 err = sum(data ~= rxBit); ber = err / numBits; fprintf('Eb/N0 = %.2f dB, BER = %.6f\n', EbN0_dB, ber);awgn(sym, EbN0_dB, 'measured')的含义是:先把输入信号sym的实测功率作为信号功率,然后按照EbN0_dB计算出加性高斯白噪声的方差并叠加。第三参数写'measured'时,信号功率不需要自己算;如果省略它,函数默认信号功率为 0 dBW。这里 BPSK 信号幅度是 ±1,平均功率正好为 1,两种写法结果一样。但当你换成 8PSK 或 16QAM 时,星座点功率不一定为 1,'measured'能自动规避功率不匹配问题。
注意,awgn()里的第二个参数是 Eb/N0 还是 SNR,取决于你对sym的定义。对于 BPSK,一个符号携带 1 比特,Eb/N0 就是 SNR。如果改成qammod生成 16QAM 符号,awgn()的第二个参数仍是 SNR(即 Es/N0),要算 Eb/N0 需要再减去10*log10(4)。这个细节和 Simulink 模块里的 Mode 选择是同一个坑,两头要对应起来。
3.2 手动生成高斯噪声并验证功率
有时候你需要自己加噪,比如想用不同分布或想控制噪声实现层级的复杂度。手动加噪的核心公式是:rx = sym + sqrt(No/2) * randn(size(sym)),其中No是噪声功率谱密度,randn生成方差为 1 的实高斯白噪声。为什么是sqrt(No/2)?因为实信号的单边功率谱密度为No/2,噪声总功率等于No * fs的一半,在基带等效模型中,你只需要把噪声方差设为No/2。
%% 手动加噪:精确控制噪声方差 EbN0_dB = 6; EbN0 = 10^(EbN0_dB/10); % 线性值 Eb = 1; % BPSK 每比特能量为 1 No = Eb / EbN0; % 噪声单边功率谱密度 noiseVar = No/2; % 实噪声方差 noise = sqrt(noiseVar) * randn(numBits, 1); rx_manual = sym + noise; % 对比 awgn 函数的结果 rx_awgn = awgn(sym, EbN0_dB, 'measured'); fprintf('手动加噪与实际误码率: %.6f\n', sum((rx_manual>0)~=data)/numBits); fprintf('awgn 函数误码率: %.6f\n', sum((rx_awgn>0)~=data)/numBits);这段代码里的noiseVar = No/2是最容易让人犹豫的行。因为有的教材写sqrt(No/2),有的写sqrt(No),区别在于信号是复数还是实数模型。BPSK 的基带信号是实数,噪声分布是实高斯,方差为No/2。QPSK 则是复信号,同相和正交两路各加方差为No/2的实噪声,叠加成复噪声方差为No,此时写sqrt(No/2)*(randn+1j*randn)。判断原则很简单:看你的调制信号是实数列还是复数列,复数列就必须拆两路。
3.3 Simulink 与脚本统一定标:模型里填的功率单位
Simulink 的 AWGN Channel 模块内部处理噪声时,同样遵循上述公式。它要求填入Input signal power的作用是不用实测信号功率,直接按设定值计算。如果你把模块里的 Eb/N0 设为 6dB,并把Input signal power设为 1,那模块内部就执行一遍与我们手动代码完全一致的加噪操作。为了验证,我习惯在模型里加一个To Workspace模块记录信道输出,然后回到 MATLAB 里和awgn()的输出做互相投影对比。
一个常见的工程做法是:先用 MATLAB 纯代码调通链路,再用 Simulink 搭模型做可视化或生成代码。两个环境的参数是同一个数学模型,只是入口不同。下面的表格概括了三种做法的功率语义:
| 实现方式 | 噪声功率计算公式 | 需要提供的参数 |
|---|---|---|
awgn(x, snr, 'measured') | No = x_power / 10^(snr/10) | 信噪比 dB 值 |
手动randn | noiseVar = No/2,其中No = Eb/EbN0 | Eb 值、Eb/N0 线性值 |
| Simulink AWGN 模块 (Eb/N0) | 同上,但取Eb=1(默认) | Eb/N0 dB 值、每符号比特数 |
这条对应关系是排查误差的钥匙。如果 Simulink 仿出来的 BER 和 MATLAB 代码差很大,先检查是不是每符号比特数填错了。16QAM 的模块填错这个数字,噪声功率会差 12 倍,BER 曲线直接错几个数量级。
4. 读懂仿真结果:BER 曲线、理论下限和信噪比换算
4.1 理论 BER 曲线怎么画
仿真的最终目的通常是将实测 BER 与理论 BER 对比。BPSK 在 AWGN 下的理论误码率是0.5 * erfc(sqrt(EbN0_lin)),此公式只适用于相干解调 BPSK。但在工程上,我们更常用berawgn()函数,它能直接给出多种调制的理论值,省得每次查公式。
%% 绘制理论 BER 与仿真 BER EbN0_dB = 0:0.5:10; EbN0_lin = 10.^(EbN0_dB/10); theory_ber = 0.5 * erfc(sqrt(EbN0_lin)); % BPSK 理论值 % 把第二节扫到的仿真结果画在一起 semilogy(EbN0_dB, theory_ber, 'r-', 'LineWidth', 1.5); hold on; semilogy(EbN0_list, ber_results, 'bo-'); legend('理论 BPSK', 'Simulink 仿真'); xlabel('Eb/N0 (dB)'); ylabel('BER'); title('AWGN 信道 BPSK 误码率曲线'); grid on;这段代码里最关键的是把纵轴设为对数刻度,否则 BER 从10^-1到10^-5的动态范围会被“压缩”到肉眼看不见的下方。erfc是余误差函数,对 BPSK 来说这是精确的数学表达,不是近似。如果你画出来的仿真点整体比理论值高 0.5dB 左右,先怀疑噪声功率是否多给了;如果低信噪比段对齐,高信噪比段却突然掉不下去,那是仿真比特数不够,误码率在地板附近波动。
4.2 从 SNR 换算 Eb/N0 的四个参数
工程里经常碰到的问题是:手头只有接收机输出信噪比 SNR,想和 BER 曲线对齐。换算公式是Eb/N0 (dB) = SNR (dB) + 10*log10(Ts) - 10*log10(Tb),或者写成Eb/N0 = Es/N0 - 10*log10(log2(M))。这里M是调制进制数。对于 BPSK,log2(2)=1,所以 SNR 和 Eb/N0 相等。但对于 QPSK,M=4,Eb/N0 比 Es/N0 低 3dB。
实际中还有一个容易被忽略的量:当信号经过升余弦滚降滤波后,占用的带宽变成fs*(1+alpha),此时噪声功率与采样率、滤波器带宽有关。如果你用 Simulink 的 Spectrum 模块看到噪声底抬高,不要急着调模块参数,先算一下等效噪声带宽。我在做 DVB-S2 仿真时习惯在脚本里先定义Es = mean(abs(sym).^2),再显式计算 Es/N0,最后换算 Eb/N0,这样能避免把成形滤波器的 3dB 损耗算错。
4.3 仿真结果异常的排查清单
遇到仿真点和理论值对不上,按下面的顺序查,通常能解决 90% 的问题:
- 误码率比较器的
Tx接入点:是否经过延迟对齐。Simulink 里Error Rate Calculation的Receive delay参数默认是 0,但信道模块、滤波器都会引入延迟,最常见的错误是信噪比低时误码率正常,信噪比高时误码率降到1e-2后不再降,这就是比特序没对齐。 - 噪声模式是否选错:模块里选了
SNR但要填 Eb/N0,或者选了Eb/No但每符号比特数写错,导致噪声功率增加10*log10(M)倍。 - 信号功率是否等于 1:
Input signal power填 1,但实际信号经过插值或增益后平均功率变成 0.8,实际信噪比低于设定值 0.97dB。 - 仿真数据长度是否足够:BER 在
1e-5时,至少要跑 10^6 个符号才能看到 10 个错误,否则统计抖动很大。常用经验是“目标误码率的倒数乘 20”,即想看到1e-5,跑2e6个比特。
关于延迟对齐,我提供一个小技巧:把Error Rate Calculation的Receive delay设置为 1,然后观察误码率是否显著变化。如果变化了,说明调制器或解调器内部有固有延迟。BPSK 调制解调模块通常没有延迟,但如果你加了平方根升余弦滤波器,延迟就是滤波器阶数的一半。更好的办法是先用alignsignals函数在 MATLAB 里检查两路比特信号,找出延迟量,再回填到 Simulink 模块里。
5. 把仿真当工程用:三个提高 AWGN 模型可信度的技巧
5.1 用 Simulink 模型参数批量跑对应关系,而不是手动改
在完成模型验证后,我建议把整个模型封装成子系统,然后用set_param直接修改模块里的参数。例如要把 AWGN 模块的 Eb/N0 改成工作区变量,可以在初始化回调里写assignin('base','EbN0_dB',2)。这样你从 MATLAB 脚本每次修改base工作区的值,再调用sim()就不需要每次打开模型面板。技巧是给 AWGN 模块的掩码参数分配一个变量名,比如EbN0_dB,然后用下面这段脚本循环调用:
%% 批量跑 BER 的另一种方式:直接改模块参数 for nn = 1:length(EbN0_list) set_param('awgn_simu/AWGN Channel', 'EbNo', 'EbN0_dB'); % 将参数变量化 EbN0_dB = EbN0_list(nn); % 重新赋值 simOut = sim('awgn_simu'); ber_results(nn) = simOut.ber(1); endset_param里的第三参数'EbN0_dB'其实是指模块内部的字符串,运行到这条命令时 Simulink 会把这个字符串当作表达式解析,于是base工作区中的EbN0_dB就被读入。这种方式的优点是扫点过程中不用反复打开模型,也能配合parfor并行计算提速。缺点是如果你把EbNo写错成SNR,参数名不匹配会直接报错,所以需要先跑一次单点确认路径。
5.2 开启外部模式并在运行时观察中间量
当你想看调制器输出星座点在噪声叠加前后的变化,或者想看信道输出信号的频谱时,打开 Simulink 的 “External” 仿真模式(需要目标硬件或仿真器支持)。在纯软件仿真中,更实用的做法是把几个关键信号用To Workspace模块输出,然后在 MATLAB 里用plot或scatter画星座图。我在调试 AWGN 模型时,会先画叠加前的星座图确认调制正确,再画叠加后的星座图看噪声是圆形还是菱形。圆形说明同相和正交噪声方差一致,菱形说明某个支路噪声功率不对。
5.3 从模型到 C 代码:生成后的数值一致性检查
如果 AWGN 模型最终要部署到 FPGA 或嵌入式处理器,你多半需要从 Simulink 模型生成 C 代码。生成时有个隐藏坑:AWGN 模块内部用了随机数生成器,默认是v4种子算法,每次仿真都会得到不同的噪声序列。为保证可重复性,你必须在模型配置参数里把随机数种子固定,或者在生成代码时用可配置的种子参数。我通常会在模型的InitFcn里调用rng(42)来固定整个仿真的全局随机数流。生成代码后,用同样的输入文件跑一次,对比软件仿真和硬件在环的 BER 曲线,两者应在统计误差范围内一致。如果出现系统偏差,多半是硬件环境里噪声生成算法被优化过了头,比如把sqrt(No/2)简化成了sqrt(No),这时候用前面提到的手动加噪公式回推就能定位。
以上就是围绕这个标题能展开的全部核心内容。从 Simulink 模型搭建到 MATLAB 代码复现,再到参数换算和排错,关键是保持对“信号功率、噪声方差、Eb/N0”三者换算的清醒。把这个关系刻在脑子里,任何 AWGN 相关仿真都不会跑偏。
本文还有配套的精品资源,点击获取