简介:这份资源面向通信工程、电子信息类专业学生及数字通信仿真入门者,提供基于MATLAB Simulink的完整数字通信链路仿真工程,重点覆盖QPSK与16QAM两种调制模式下的链路计算与性能对比。包内共46个文件,以slx与slxc仿真模型、mat数据文件为主,辅以xml配置、r2017b版本备份及ReadMe说明,压缩包约541KB,结构紧凑便于直接运行与二次修改。资源围绕信号产生、调制、AWGN与衰落信道模拟、解调及误码率评估展开,可观察星座图、BER曲线与功率参数变化,帮助理解不同调制方式在频谱效率与抗噪性能上的差异。已有277人学习下载,适合作为课程设计、毕业设计或自学数字通信链路的参考范例,通过修改信噪比、码率等参数即可完成链路优化实验,快速建立从理论到仿真的直观认识。
1. 从零搭一条 QPSK/16QAM 数字通信链路:Simulink 里到底在算什么
很多人第一次在 Simulink 里拖出 QPSK 调制模块,跑完发现星座图是一团糊,误码率曲线跟理论值差着好几个数量级,然后开始怀疑是不是 MATLAB 版本不对、是不是工具箱没装全。其实绝大多数翻车都不是软件问题,而是链路里某个环节的采样率、帧结构或者信噪比定义没对齐。这篇笔记讲的就是怎么用 Simulink 把一条包含 QPSK 和 16QAM 两种模式的数字通信链路完整跑通,并且让误码率曲线能跟理论值对上。适合已经会基本 MATLAB 操作、想用 Simulink 做通信系统级验证的工程师和学生。整条链路会覆盖信源、信道编码前的比特映射、脉冲成型、AWGN 信道、匹配滤波、判决解调、误码统计这几个核心环节,两种调制模式通过一个开关切换,方便对比。
2. 链路架构怎么定:QPSK 和 16QAM 共用什么、分开什么
2.1 先想清楚哪些模块能复用
搭链路之前最容易犯的错是给 QPSK 和 16QAM 各画一套图,结果改一个参数要改两处,跑对比实验时两边条件根本不一致。正确的做法是先抽象出两种调制方式共享的部分。信源产生、比特到符号的映射框架、脉冲成型滤波器、AWGN 信道、匹配滤波器、误码率统计这些环节,QPSK 和 16QAM 是完全一样的,区别只在映射表、星座点数量和每符号携带的比特数。
QPSK 每符号 2 比特,星座点落在四个象限的对角线上,归一化后坐标是 ±1/√2 ± j/√2。16QAM 每符号 4 比特,星座点是 4×4 的方格,归一化后坐标是 (±1, ±3) 和 (±3, ±1) 的组合再除以 √10。这个归一化系数很关键,它保证两种调制方式的平均符号功率都是 1,否则你在同一个信噪比定义下比较误码率就没有意义。
共享的部分用 Simulink 的子系统封装起来,调制方式相关的部分做成可切换的变体子系统。变体子系统的激活条件用一个工作区变量mod_mode控制,取值'QPSK'或'16QAM'。这样跑蒙特卡洛的时候只需要在脚本里改这个变量,不用动模型结构。
2.2 采样率和符号率的比例关系
这是新手翻车最集中的地方。Simulink 里每个模块都有自己的采样时间,如果信源出来的采样时间和后面脉冲成型滤波器的采样时间对不上,Simulink 要么报维度错误,要么悄悄做速率转换,结果星座图就散了。
我一般这样定:设符号率为Rs,每个符号采sps个点,那么系统的基础采样率就是Fs = Rs * sps。sps取 4 或 8 都行,取 4 计算量小,取 8 脉冲成型更平滑。信源产生的是比特流,经过符号映射后变成符号序列,符号序列的采样时间应该是1/Rs。进入脉冲成型滤波器之前,用 Simulink 的Upsample模块把符号序列升采样到Fs,升采样因子就是sps。脉冲成型滤波器的采样时间设为1/Fs。
接收端匹配滤波器的系数跟发送端成型滤波器一样,采样时间也是1/Fs。滤波完之后做下采样,下采样因子还是sps,恢复出符号率的序列,再送进判决和解调。整个链路里只要有一个环节的采样时间设错,星座图就会从点变成云。
2.3 信噪比到底加在哪一级
AWGN 信道模块的Eb/N0和SNR是两个不同的参数,选错了曲线就对不上理论值。数字通信里习惯用Eb/N0,因为它跟调制方式无关,QPSK 和 16QAM 在同一个Eb/N0下比较才有意义。但 Simulink 的 AWGN 模块如果选SNR模式,它算的是符号功率和噪声功率的比,16QAM 每符号 4 比特,同样的SNR对应的Eb/N0比 QPSK 低 3 dB 左右。
我的做法是 AWGN 模块选Eb/N0模式,然后手动填入每符号比特数。QPSK 填 2,16QAM 填 4。模块内部会根据输入信号的功率和这个比特数换算出噪声方差。这里有个细节:AWGN 模块的输入信号功率如果是 1(归一化之后),那么它算出来的噪声方差就是N0/2每维。如果你在模块前面又乘了一个增益,功率就不是 1 了,噪声方差会跟着变,曲线整体平移。所以要么保证进 AWGN 之前信号功率严格是 1,要么用var参数手动指定噪声方差。
3. 在 Simulink 里把 QPSK 链路跑通的最小步骤
3.1 建模前的参数准备
打开 MATLAB,先在脚本里把参数算好,不要直接在模块对话框里敲数字。这样后面跑蒙特卡洛循环的时候只需要改脚本里的变量。
% 链路基础参数 Rs = 1000; % 符号率 1000 symbol/s sps = 8; % 每符号采样点数 Fs = Rs * sps; % 系统采样率 8000 Hz M_qpsk = 4; % QPSK 调制阶数 M_16qam = 16; % 16QAM 调制阶数 k_qpsk = log2(M_qpsk); % 每符号比特数 2 k_16qam = log2(M_16qam); % 每符号比特数 4 % 脉冲成型滤波器:根升余弦 rolloff = 0.35; % 滚降系数 span = 6; % 滤波器跨越符号数 rrcFilter = rcosdesign(rolloff, span, sps, 'sqrt'); % 信噪比扫描范围 EbN0_dB = 0:2:12; EbN0_lin = 10.^(EbN0_dB/10);rcosdesign的第三个参数sps必须跟链路里的升采样因子一致,否则滤波器的频响跟符号率对不上。span取 6 意味着滤波器长度是6*8+1=49个抽头,这个长度在sps=8时能提供足够的旁瓣抑制,再长收益不大,再短码间干扰会明显。
3.2 搭建发送端
新建一个 Simulink 模型,按下面的顺序放模块。
信源用Bernoulli Binary Generator,采样时间设1/Rs,产生 0/1 比特流。后面接一个Buffer模块,把比特流按每k个比特一组缓冲,QPSK 时k=2,16QAM 时k=4。Buffer 的输出是k列的矩阵,每行是一个符号对应的比特组。
接下来是符号映射。Simulink 自带的QPSK Modulator Baseband和Rectangular QAM Modulator Baseband可以直接用,但要注意它们的星座图映射方式。QPSK 模块默认是格雷映射,16QAM 模块默认也是格雷映射,这跟理论误码率公式的假设一致。如果你自己写映射表,一定要保证相邻星座点只差一个比特,否则误码率会比理论值高。
映射模块的采样时间设1/Rs。输出是复数符号序列。然后接Upsample模块,升采样因子sps,输出采样时间变成1/Fs。再串Raised Cosine Transmit Filter,滤波器系数用工作区里的rrcFilter,采样时间1/Fs。
发送端最后接一个Gain模块,把信号功率归一到 1。因为rrcFilter的增益不是 1,滤波之后功率会变。归一化因子可以在脚本里算:gain_norm = 1/sqrt(sum(rrcFilter.^2)/sps)。这个细节不做的话,后面 AWGN 的Eb/N0就不准。
3.3 信道和接收端
AWGN Channel 模块的Mode选Eb/N0,Eb/N0值从工作区变量EbN0_dB读,Number of bits per symbol填k。输入信号功率选Power模式,值填 1,跟前面的归一化对应。
接收端先接Raised Cosine Receive Filter,系数跟发送端一样,采样时间1/Fs。然后接Downsample模块,因子sps,输出采样时间回到1/Rs。再进判决解调模块,QPSK 用QPSK Demodulator Baseband,16QAM 用Rectangular QAM Demodulator Baseband,判决方式选Hard Decision。
解调出来是比特流,接Error Rate Calculation模块,接收端接解调输出,发送端接信源输出经过一个Delay模块对齐的参考比特。Error Rate Calculation的Receive delay要设成链路总延迟,这个延迟包括滤波器群延迟和缓冲延迟,一般设span*sps/2 + k左右,具体值跑一次看误码率是不是从 0.5 开始降,如果是 0.5 说明没对齐。
3.4 用脚本驱动仿真并画误码率曲线
模型搭好之后不要手动点运行,用脚本循环扫EbN0,每次把结果存下来。
% 蒙特卡洛仿真主循环 numSym = 1e5; % 每次仿真的符号数 ber_qpsk = zeros(size(EbN0_dB)); ber_16qam = zeros(size(EbN0_dB)); for idx = 1:length(EbN0_dB) EbN0 = EbN0_dB(idx); % QPSK 模式 mod_mode = 'QPSK'; k = k_qpsk; sim('digital_link_model'); ber_qpsk(idx) = ber_out(1); % 16QAM 模式 mod_mode = '16QAM'; k = k_16qam; sim('digital_link_model'); ber_16qam(idx) = ber_out(1); end % 理论误码率 ber_theory_qpsk = qfunc(sqrt(2*EbN0_lin)); ber_theory_16qam = (4/k_16qam)*(1-1/sqrt(M_16qam))* ... qfunc(sqrt(3*k_16qam*EbN0_lin/(M_16qam-1))); semilogy(EbN0_dB, ber_qpsk, 'bo-', EbN0_dB, ber_theory_qpsk, 'b--', ... EbN0_dB, ber_16qam, 'rs-', EbN0_dB, ber_theory_16qam, 'r--'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER'); legend('QPSK 仿真','QPSK 理论','16QAM 仿真','16QAM 理论');sim函数调用模型时,模型里的To Workspace模块会把误码率结果写到工作区变量ber_out。numSym取 1e5 在低信噪比时够用,高信噪比时误码率可能到 1e-5 以下,需要加大到 1e6 才能看到统计意义。跑一次完整扫描大概几分钟,取决于模型复杂度和sps。
4. 16QAM 模式切换时最容易翻车的四个地方
4.1 星座图归一化系数漏乘
现象是 16QAM 的误码率曲线整体比理论值高 3 到 4 dB,形状是对的,就是平移了。原因是 16QAM 映射模块输出的符号平均功率不是 1。矩形 16QAM 的星座点坐标是 ±1 和 ±3,平均功率是(1+1+9+9)/4 = 5,所以归一化系数应该是1/sqrt(5),不是1/sqrt(10)。我一开始记成1/sqrt(10),因为有些教材用的是每维归一化,那个系数对应的是每维功率。Simulink 的Rectangular QAM Modulator Baseband模块有个Normalization method参数,选Average Power它会自动归一化,选Minimum distance between symbols就要自己算。我一般选Average Power,省得记错。
4.2 每符号比特数在 AWGN 模块里没跟着切
现象是切到 16QAM 之后误码率曲线跟 QPSK 几乎重合,没有预期的 3 dB 恶化。原因是 AWGN 模块的Number of bits per symbol还停在 2。这个参数决定了模块怎么把Eb/N0换算成噪声方差,填 2 的时候它以为每符号 2 比特,算出来的噪声功率比实际需要的低,等效于信噪比偏高。解决办法是在变体子系统里把 AWGN 模块也做成变体,或者用set_param在脚本里动态改这个参数。我习惯在脚本里用set_param('digital_link_model/AWGN Channel','BitsPerSymbol',num2str(k))每次仿真前设一下。
4.3 滤波器群延迟导致误码统计错位
现象是误码率在低信噪比时正常,高信噪比时卡在 1e-2 降不下去。原因是Error Rate Calculation的Receive delay设小了,接收端解调出来的比特跟发送端参考比特错开了几个符号。根升余弦滤波器的群延迟是span*sps/2个采样点,换算到符号率就是span/2个符号。加上 Buffer 和映射的延迟,总延迟大概是span/2 + 2个符号。我一般先设一个偏大的值,跑一次看误码率曲线是不是单调下降,如果是就慢慢减小到刚好对齐。Simulink 有个Find Delay模块可以自动测延迟,但通信链路里信号是随机的,自动测有时候不准,还是手动调靠谱。
4.4 高信噪比时仿真符号数不够
现象是Eb/N0大于 10 dB 之后误码率曲线开始抖动,有时候突然跳到 0。原因是numSym不够,误码事件太少,统计涨落大。16QAM 在Eb/N0=12 dB时理论误码率大概在 1e-4 量级,要观察到 10 个误码需要至少 1e5 个符号,要曲线平滑需要 1e6 以上。解决办法是把numSym设成 1e6,或者在高信噪比段用Error Rate Calculation的Stop simulation功能,累计到一定误码数就停。我一般设max_num_errors = 100,到了就停,能省不少时间。
5. 让曲线跟理论对齐的验证方法和一个提速技巧
5.1 分阶段验证,不要一上来就看端到端误码率
链路搭好之后直接跑端到端误码率,如果对不上,你根本不知道是映射错了、滤波错了还是同步错了。我的习惯是分三段验证。
第一段,去掉脉冲成型和匹配滤波,映射完直接进 AWGN,解调完统计误码。这时候曲线应该跟理论值几乎重合,如果差超过 0.5 dB,问题在映射或信噪比定义。第二段,加上脉冲成型和匹配滤波,但信道先不加噪声,看星座图是不是干净的点。如果星座图散开,问题在滤波器系数或采样率。第三段,加噪声跑端到端,这时候如果曲线跟理论差在 1 dB 以内,说明整条链路是通的。
这个分阶段方法能帮你快速定位问题在哪一级,比对着一个错误的端到端曲线瞎猜高效得多。
5.2 用 Accelerator 模式提速蒙特卡洛
Simulink 默认的 Normal 模式每步都解释执行,跑 1e6 个符号要等很久。把模型改成Accelerator模式,Simulink 会先把模型编译成 C 代码再跑,速度能快 5 到 10 倍。设置方法是在模型窗口菜单里选Simulation→Mode→Accelerator,或者在脚本里用set_param('digital_link_model','SimulationMode','accelerator')。
如果还要更快,可以用parfor并行扫Eb/N0点。每个Eb/N0点的仿真互相独立,开 4 个 worker 就能快 4 倍。注意parfor里不能用sim函数直接调同一个模型,需要给每个 worker 复制一份模型文件,或者用Simulink.SimulationInput对象来管理并行仿真。我一般用后者,代码稍微复杂一点但不用复制文件。
% 并行扫频示例 parpool(4); parfor idx = 1:length(EbN0_dB) in = Simulink.SimulationInput('digital_link_model'); in = in.setVariable('EbN0_dB', EbN0_dB(idx)); in = in.setVariable('mod_mode', 'QPSK'); out = sim(in); ber_par(idx) = out.ber_out(1); endSimulink.SimulationInput的好处是每个 worker 拿到的是独立的参数集,不会互相干扰。跑完之后把ber_par跟理论曲线画在一起,如果两条线在 1 dB 以内吻合,这条链路就算调通了。
5.3 一个容易被忽略的细节:随机种子
蒙特卡洛仿真里信源的随机种子如果每次仿真都一样,不同Eb/N0点之间的误码事件是相关的,曲线会显得比实际平滑。我一般用rng('shuffle')在每次仿真前重置种子,或者用RandStream给每个Eb/N0点分配独立的流。这样统计出来的误码率才是独立的。这个细节在写论文或做对比实验时很重要,审稿人有时候会问。
最后说个我自己的习惯:每次改完模型参数,先跑一个EbN0=4 dB的单点,看误码率是不是在理论值附近。如果是,再跑全扫描;如果不是,先查这个点,别浪费时间跑完整曲线。这个习惯帮我省了很多次跑完半小时才发现参数设错的血泪时间。希望帮到你。
本文还有配套的精品资源,点击获取