Simulink数字通信链路实战:QPSK与16QAM误码率对齐指南 简介这份资源面向通信工程、电子信息类专业学生及数字通信仿真入门者提供基于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 填 216QAM 填 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*8149个抽头这个长度在sps8时能提供足够的旁瓣抑制再长收益不大再短码间干扰会明显。3.2 搭建发送端新建一个 Simulink 模型按下面的顺序放模块。信源用Bernoulli Binary Generator采样时间设1/Rs产生 0/1 比特流。后面接一个Buffer模块把比特流按每k个比特一组缓冲QPSK 时k216QAM 时k4。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/N0Eb/N0值从工作区变量EbN0_dB读Number of bits per symbol填k。输入信号功率选Power模式值填 1跟前面的归一化对应。接收端先接Raised Cosine Receive Filter系数跟发送端一样采样时间1/Fs。然后接Downsample模块因子sps输出采样时间回到1/Rs。再进判决解调模块QPSK 用QPSK Demodulator Baseband16QAM 用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平均功率是(1199)/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/N012 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点分配独立的流。这样统计出来的误码率才是独立的。这个细节在写论文或做对比实验时很重要审稿人有时候会问。最后说个我自己的习惯每次改完模型参数先跑一个EbN04 dB的单点看误码率是不是在理论值附近。如果是再跑全扫描如果不是先查这个点别浪费时间跑完整曲线。这个习惯帮我省了很多次跑完半小时才发现参数设错的血泪时间。希望帮到你。本文还有配套的精品资源点击获取