恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
pi/4-QPSK+Turbo误码率仿真:Matlab链路实现与参数分析
首页
资讯中心
/
pi/4-QPSK+Turbo误码率仿真:Matlab链路实现与参数分析
pi/4-QPSK+Turbo误码率仿真:Matlab链路实现与参数分析
发布时间:2026/10/9 23:29:36
简介一套基于pi/4-QPSK调制解调与Turbo编译码的通信链路MATLAB误码率仿真程序适合通信工程、电子信息类专业学生及科研人员用于课程设计、毕业设计或算法验证。仿真流程完整覆盖随机二进制序列生成、Turbo编码、pi/4-QPSK调制、AWGN信道加噪、解调、译码及误码率统计等环节代码配有中文注释并附带程序操作视频便于快速掌握运行环境和参数配置降低调试门槛。资源包共10个文件其中7个m脚本包含主程序与Turbo编解码函数2个mat数据文件用于保存参考结果另有1个txt说明文档压缩包整体仅41KB轻量清晰。目前已有113人学习下载适合在MATLAB 2022a等环境下直接运行通过调整信噪比即可观察误码率变化也可将Turbo编码与pi/4-QPSK解调模块分离复用为通信链路级仿真提供可扩展的实现框架。1. 基于pi/4-QPSKTurbo的Matlab误码率仿真这套链路到底解决什么问题通信链路仿真的价值不在省掉硬件而在把调制、编码、信道、解调、译码每个环节的损伤单独暴露出来。pi/4-QPSK是一种相位差分调制方式相比标准QPSK它的信号点相位跳变被限制在±π/4和±3π/4之间不会出现180度突变对非线性功放和衰落信道更友好Turbo码则通过两个递归系统卷积码RSC和迭代译码逼近信道容量。把这两个技术放在一条链路里是卫星移动通信、数字集群系统里非常经典的物理层方案。这篇笔记要讲的就是怎么在Matlab里把这条链路完整搭起来跑出误码率曲线并且拿到带中文注释的程序和操作视频让链路可复现、可修改。适合两类人一是通信方向的学生课程设计或毕业设计需要一套能跑通的误码率仿真二是做物理层算法验证的工程师需要快速评估pi/4-QPSK配合不同Turbo参数在AWGN信道下的性能边界。2. pi/4-QPSK调制解调差分相位为什么能省掉载波同步2.1 pi/4-QPSK和QPSK的差别相位跳变被压住了标准QPSK的四个星座点按π/2间隔分布相邻符号之间可能出现180度相位跳变。这个跳变带来的问题是信号包络会在过零点被压缩到接近0经过带限滤波器后包络起伏明显变大射频端非线性功放会把这种幅度起伏转化为频谱扩展恶化邻信道泄漏。pi/4-QPSK的改进思路是把星座点分成两组A组在0、π/2、π、3π/2B组在π/4、3π/4、5π/4、7π/4奇偶符号从不同组里取点。这样相邻符号的相位差只能是π/4或3π/4最大跳变限制在135度以内包络波动比QPSK小很多。另一个实际收益是解调方式更灵活。因为相位差携带信息接收端可以用差分检测不需要恢复绝对载波相位也不存在QPSK相干解调里的四象限相位模糊问题。代价是差分检测在AWGN信道下有大约2到3dB的性能损失这在后面Turbo码配合时要格外注意。2.2 发射端Matlab实现差分编码和相位累加我一般把发射端分成三步比特映射、差分相位累加、脉冲成形。输入数据先按2比特一组映射成相位增量然后和前一个符号的相对相位累加得到当前符号的绝对相位这个绝对相位再对应到星座点坐标。关键代码模块如下% 发射端参数 M 4; % QPSK Rb 9600; % 信息比特率 9600 bps Fs 19200; % 采样率 19200 Hz匹配后续滤波 sps Fs / Rb; % 每比特采样数这里等于2 % 比特流转相位增量00-0 01-pi/2 11-pi 10--pi/2 % 这里采用格雷映射相邻相位差只有1比特不同 gray_map [0 1 3 2]; % 二进制到格雷码顺序 dibit bi2de(reshape(bits(1:end-mod(length(bits),2)), [], 2), left-msb); delta_phase (pi/2) * gray_map(dibit1); % 相位增量序列 % 差分相位累加当前绝对相位 上一绝对相位 当前相位增量 abs_phase cumsum(delta_phase); % 星座映射取B组基准保证最大跳变135度 tx_sym exp(1j * abs_phase);逻辑说明bi2de把每2个比特转成十进制索引gray_map保证相邻符号只差1比特有利于差分检测时单比特错误不会一次带偏两个比特。cumsum完成相位累加这里不需要mod 2πMatlab的exp(1j*theta)会自动包裹相位。要注意输入比特长度必须是偶数代码里reshape之前的截断处理就是做这个约束。参数说明Rb和Fs的比例决定了plt里时域波形和频谱的分辨率sps2时一个符号只有2个采样点后续做匹配滤波前如果需要观察眼图或星座更细致的分布可以把Fs提高到Rb的8到16倍代价是仿真速度下降。成形滤波器我习惯用根升余弦RRC滚降系数0.35这个系数在带宽和峰值平均功率比PAPR之间取平衡。2.3 接收端差分解调相位差判决和软信息输出接收端最常见的做法是把下变频后的基带信号做匹配滤波然后按符号周期采样得到复数符号序列。差分解调利用当前符号和上一符号的共轭相乘得到相位差再判决到最近的π/2整数倍相位上% 基带差分相位检测 % rx_sym是匹配滤波后按符号周期采样的复数序列 phase_diff angle(rx_sym(2:end) .* conj(rx_sym(1:end-1))); % 映射判决把相位差映射到0、pi/2、pi、-pi/2 % 这里利用复数旋转简化乘exp(-j*pi/4)后按四象限判决 rotated exp(1j * (phase_diff - pi/4)); dec_dibit zeros(size(phase_diff)); dec_dibit(real(rotated) 0 imag(rotated) 0) 0; % 00 dec_dibit(real(rotated) 0 imag(rotated) 0) 1; % 01 dec_dibit(real(rotated) 0 imag(rotated) 0) 3; % 11 dec_dibit(real(rotated) 0 imag(rotated) 0) 2; % 10逻辑说明conj(rx_sym(1:end-1))是上一符号的共轭复数相乘的相位就是当前符号相对上一符号的相位差。旋转π/4是为了把pi/4-QPSK的两组星座点映射到标准QPSK的四个象限上让判决边界落在坐标轴上这样四象限判决直接可用。参数说明这段代码输出的是硬判决比特。如果后面接的是普通卷积码硬判决够用但接Turbo译码器时硬判决会丢软信息Turbo的迭代增益基本发挥不出来。所以实际链路里我会在这里输出对数似然比LLR而不是硬判决具体做法在第四章讲Turbo软输入时说明。要注意差分解调的参考点是前一符号所以一个符号出错可能影响两个比特的判决这也是差分检测性能损失的一部分来源。3. Turbo编译码迭代译码为什么能逼近香农限3.1 Turbo码的核心结构两个RSC和一个交织器Turbo码的编码器由两个递归系统卷积码RSC通过交织器并联而成。第一个RSC直接编码信息序列第二个RSC编码交织后的信息序列。译码端用两个软输入软输出SISO译码器迭代交换外信息每轮迭代让两个译码器互相修正对方的置信度这是Turbo码逼近信道容量的根本机制。选择RSC而不是普通卷积码的原因在于递归结构让编码器的输出权重谱更稀疏低重量码字少迭代译码时错误平台error floor更低。RSC的生成多项式一般用八进制表示比如反馈多项式37、前向多项式21八进制也就是[1 0 0 0 1 1]和[1 0 0 0 0 1]对应的生成序列。实际工程里不同标准选的生成多项式不同但仿真阶段用这一组足够验证算法。3.2 编码器Matlab实现RSC分量码和删除矩阵% 生成多项式反馈37(oct)前向21(oct) % 对应二进制g0 [1 0 0 0 1 1] g1 [1 0 0 0 0 1] g0 [1 0 0 0 1 1]; % 反馈多项式 x^5x^2x1 g1 [1 0 0 0 0 1]; % 前向多项式 x^51 % RSC编码函数单分量 function [sys, par] rsc_encode(bits, g0, g1) state zeros(1, length(g0)-1); % 初始状态 sys zeros(size(bits)); par zeros(size(bits)); for k 1:length(bits) sys(k) bits(k); % 反馈位当前比特和状态位异或 fb mod(sum(bits(k) * g0(2:end) .* state), 2); % 校验位反馈位和状态加权异或 par(k) mod(sum(fb * g1(2:end) .* state), 2); % 状态更新右移反馈位进入最高位 state [fb, state(1:end-1)]; end end % 编码器主流程码率1/3不删余 bits_inter interleaver(bits_in); % 随机交织种子固定便于复现 [sys1, par1] rsc_encode(bits_in, g0, g1); [sys2, par2] rsc_encode(bits_inter, g0, g1); % 码率1/3系统位 两个校验位 encoded [sys1; par1; par2]; % 每一列对应一个信息比特的编码输出逻辑说明RSC编码器里反馈位fb同时参与校验位计算和状态更新这是递归结构的关键。par(k)是当前时刻的校验输出和普通卷积码最大区别在于反馈位影响后续所有时刻的状态这是Turbo码低重量码字减少的原因。参数说明交织器长度直接影响Turbo码性能。我做过对比长度256时误码率曲线在10^-4左右出现平台长度1024时平台明显下降长度4096时在仿真信噪比范围内基本看不到错误平台。随机交织器的种子要固定否则每次仿真结果不同不利于调试。删余矩阵可以按码率需求调整码率1/3不删余码率1/2时交替保留par1和par2的偶数/奇数位置恢复顺序时最容易出错后面避坑章专门讲。3.3 译码器Matlab实现Max-Log-MAP迭代译码% 软输入软输出译码Max-Log-MAP近似 % Lc为信道置信度L_a为先验LLRL_e为外信息 function L_e siso_decode(rec_sys, rec_par, L_a, g0, g1) % 前向度量、后向度量用格状图递推 % 这里用归一化简化版本状态数为 2^(约束长度-1) num_states 2^(length(g0)-1); % 分支度量计算根据接收软信息和先验LLR gamma zeros(num_states, 2); for k 1:length(rec_sys) for s 1:num_states gamma(s, 1) exp( ... ); % 输入0的分支度量 gamma(s, 2) exp( ... ); % 输入1的分支度量 end % 前向递推 alpha % 后向递推 beta % 计算后验LLR减去先验得到外信息 end end % 迭代译码主循环 L_e1 zeros(size(encoded_bits)); % 分量译码器1的外信息初始为0 for iter 1:max_iter L_a2 interleaver(L_e1); % 外信息交织后作为译码器2先验 L_e2 siso_decode(rec_sys2, rec_par2, L_a2, g0, g1); L_a1 deinterleaver(L_e2); % 交织器逆操作 L_e1 siso_decode(rec_sys1, rec_par1, L_a1, g0, g1); end L_total deinterleaver(L_e2) L_e1; % 硬判决前组合所有软信息逻辑说明这里的关键是外信息的流向。第一个SISO译码器处理系统位和校验位1输出外信息外信息交织后成为第二个SISO译码器的先验LLR第二个SISO处理交织后的系统位和校验位2输出外信息再解交织回去如此往复。每一轮迭代两个分量译码器都在利用对方给出的独立信息修正判决这正是Turbo码名称的由来。参数说明max_iter一般取5到8超过8轮后性能提升非常有限反而线性增加仿真时间。Max-Log-MAP比Log-MAP少算指数和对数每个符号的译码延迟能降低一半以上性能损失在0.2到0.3dB左右。仿真阶段我建议先用Max-Log-MAP把链路跑通确认无误后再切Log-MAP看性能上界。3.4 Turbo码关键参数速查表参数推荐范围对性能的影响迭代次数5 ~ 8少于4轮时增益不够大于8轮收益可忽略交织器长度1024 ~ 4096长度越大错误平台越低但延迟和内存线性增加生成多项式反馈37、前向21八进制决定距离谱换多项式需重新仿真验证码率1/3 或基于删余的 1/2码率越低编码增益越大但频谱效率下降交织器类型随机交织固定种子规则交织在部分信噪比下会出现周期性错误这个表是我调试链路时最常回头看的一张表。特别是交织器长度和迭代次数这两个参数新手最容易一上来就取最大值结果是仿真跑了几小时曲线还不如长度1024迭代6次的好看。仿真不是做极限测试是找性能和复杂度的平衡点。4. 完整通信链路搭建与误码率仿真流程4.1 链路模块划分和函数组织一条完整的pi/4-QPSKTurbo仿真链路我习惯拆成五个独立模块信源、Turbo编码器、pi/4-QPSK调制器、AWGN信道、pi/4-QPSK差分解调器、Turbo迭代译码器。每个模块一个函数文件主脚本只负责组装和循环。模块划分干净的一个直接好处是后面想换成瑞利信道或换成LDPC码只需要改对应模块的接口其他部分完全不动。文件组织上我一般按功能命名main_ber_simulation.m是主脚本pi4qpsk_mod.m和pi4qpsk_demod_soft.m是调制解调turbo_encoder.m和turbo_decoder.m是编译码rsc_encode.m和siso_decode.m是底层的分量函数。中文注释写在每个文件头部的说明块里标注输入输出格式、是否需要归一化、以及常见调用方式方便后面回读和给其他人交接。4.2 主脚本EbN0循环和误码率统计% 主仿真脚本pi/4-QPSK Turbo 误码率仿真 % 输出BER曲线、星座图、迭代收敛图 clear; clc; close all; % 系统参数 EbN0_dB 0:0.5:4; % 信息比特信噪比范围 max_iter 6; % Turbo迭代次数 frame_len 1024; % 交织器/帧长度 num_frames 50; % 每个信噪比下的仿真帧数 code_rate 1/3; % Turbo码率不删余 for idx 1:length(EbN0_dB) EbN0 10^(EbN0_dB(idx)/10); N0 1 / EbN0; % 归一化噪声功率谱密度 total_bit_err 0; total_bit_cnt 0; for frm 1:num_frames % 信源随机比特 bits_in randi([0 1], frame_len, 1); % Turbo编码码率1/3 encoded_bits turbo_encoder(bits_in, g0, g1, interleaver); % pi/4-QPSK调制输出符号序列 tx_sym pi4qpsk_mod(encoded_bits); % AWGN信道符号能量归一化为1噪声方差N0/2 noise sqrt(N0/2) * (randn(size(tx_sym)) 1j*randn(size(tx_sym))); rx_sym tx_sym noise; % 差分解调输出软LLR llr pi4qpsk_demod_soft(rx_sym); % Turbo译码 decoded_bits turbo_decoder(llr, max_iter); % 统计误码 err sum(decoded_bits ~ bits_in); total_bit_err total_bit_err err; total_bit_cnt total_bit_cnt frame_len; end ber(idx) total_bit_err / total_bit_cnt; end % 画图 semilogy(EbN0_dB, ber, b-o); grid on; xlabel(Eb/N0 (dB)); ylabel(BER);逻辑说明循环结构上一开始有两个嵌套循环外层扫信噪比内层做多次蒙特卡洛。每帧重新生成信源比特避免同一帧数据在不同信噪比下重复使用导致统计相关性。N0的计算用了归一化假设信号功率为1那么噪声方差就是N0/2前提是调制星座的能量已经归一化。参数说明frame_len取1024是交织器长度和帧长统一为的是让统计巧合。num_frames取50帧时每个信噪比下统计10万多比特误码率在10^-4量级已经比较可信如果想看10^-5以下的误码率需要把帧数提高到200以上否则曲线末尾会抖动。EbN0_dB范围要按Turbo码的性能估计码率1/3时误码率降到10^-4大约需要1.5到2dB范围取0到4dB比较合适。4.3 解调器输出软信息的关键实现前面提到Turbo译码必须吃软信息。pi/4-QPSK差分解调器的LLR输出我采用的方法是把判决区域的相位差转换成对应两条比特路径的似然比近似用相位的正弦和余弦分量加权% pi/4-QPSK软输出差分解调 % 输入匹配滤波后符号序列rx_sym % 输出每个编码比特的LLR对数似然比 function llr pi4qpsk_demod_soft(rx_sym) % 差分相位 phase_diff angle(rx_sym(2:end) .* conj(rx_sym(1:end-1))); % 两个编码比特分别对应相位平面的I/Q分量 % 对差分相位做旋转使判决边界落在坐标轴上 rotated exp(1j * (phase_diff - pi/4)); % 第一个比特由实部决定第二个比特由虚部决定 % 用确定性近似LLR幅值越大置信度越高 llr_bit1 sqrt(2) * real(rotated) / (N0/2); % 归一化噪声方差 llr_bit2 sqrt(2) * imag(rotated) / (N0/2); % 按发射端的比特交织顺序恢复编码比特 llr reshape([llr_bit1(:).; llr_bit2(:).], [], 1); end逻辑说明rotated把星座旋转到标准QPSK坐标后实部和虚部的符号直接对应两个编码比特的硬判决幅度则反映了判决可靠程度。LLR用幅度除以噪声方差本质是匹配滤波输出的软判决统计量。虽然这个近似不如真正推导的APP概率精确但实践里配合Max-Log-MAP迭代译码性能损失在0.2dB以内实现简单得多。参数说明最后一行reshape的作用是按发射端的比特顺序输出保证和Turbo译码器的输入顺序一致。如果调制端做的是正交比特映射这里交错读取的顺序必须严格匹配否则译码性能会直接崩掉这个细节是我自己在调试中踩过的坑当时误码率突然从10^-3跳到0.5排查了一整天才发现是比特顺序反了。4.4 操作视频对应的运行路径配套的操作视频本质上就是把上面这套流程演示一遍打开Matlab切到工程目录运行主脚本观察星座图和误码率曲线再修改几组参数重跑对比。实操里建议按三个步骤来第一步只跑一个信噪比点比如2dB确认单帧能正常译码避免一上来跑全曲线浪费时间第二步跑完整曲线观察趋势是否单调下降第三步改交织器长度或迭代次数对比性能差异。这个习惯看着简单但能避免大部分低级错误带来的无效仿真。5. 误码率仿真避坑清单5个最容易翻车的环节5.1 误码率曲线在高信噪比时进入平台期不再下降现象EbN0超过3dB后BER曲线不再下降像被一个水平线托住一样和理论预期差一个量级以上。原因是多方面的最常见的两个一是每帧数据量太少统计误差导致误码率下限被抬高二是差分检测带来的固有性能限制pi/4-QPSK差分解调的BER在10^-5附近会明显变缓。解决先增加帧数到200确认是否是统计抖动然后对照相干解调的误码率看差值是否稳定在2~3dB范围内。如果增加帧数后曲线仍然平再从编码侧入手检查是否出现了迭代不收敛导致的错误平台。5.2 删余矩阵恢复顺序错位导致译码全错现象码率从1/3改成1/2后误码率直接从良好变成0.5左右有时改一个参数就好了有时怎么调都不对。原因几乎总是删余后的校验位在接收端恢复原始顺序时错位。解决我后来给自己定了个规矩删除矩阵用独立的函数管理同一份矩阵同时用于删除和恢复杜绝手写两遍可能引入的不一致。恢复后立刻做一次静态检查比对删余位置和恢复位置是否一一对应确定无误再进入译码流程。5.3 解调器输出了硬判决Turbo迭代失效现象链路里有Turbo码但增益不明显误码率曲线和没用编码时差不多迭代次数改了也没用。原因是解调器输出的是硬判决比特而不是软信息Turbo译码器的输入置信度全部相同外信息迭代等于空转。解决在解调器输出端检查数据类型硬判决是0/1整数软LLR是浮点数。如果发现是硬判决按第四章的pi4qpsk_demod_soft改成软输出。这个坑特别隐蔽因为链路能跑通只是性能差看起来像某个参数不对实际是信息量在接口处被丢了。5.4 信噪比口径不统一EbN0和EsN0混用现象误码率曲线的横轴和论文对不上特别是在改码率之后曲线整体平移了约1.8dB。原因是EbN0和EsN0混用码率1/3时EsN0比EbN0低10*log10(3)≈4.77dB差异非常大。解决统一用信息比特信噪比EbN0作为横轴。编码后每符号的信噪比EsN0和EbN0的换算关系是EsN0 EbN0 10log10(code_rate * bits_per_symbol)。pi/4-QPSK每个符号2个比特码率1/3时每个信息比特对应3个编码比特所以EsN0 EbN0 10log10(2/3) ≈ EbN0 - 1.76dB。我在主脚本里专门留了一段注释记录这个换算。5.5 交织器种子不固定导致结果不可复现现象同样的参数两次运行得到的误码率曲线不同差别在小信噪比时尤其明显。原因是交织器使用随机数生成且未固定种子。Turbo码对交织模式敏感不同的交织器产生的性能差异在低信噪比下可能超过0.5dB。解决在代码最开头固定随机数种子rng(2024)并关闭对交织器的随机扰动。这里要特别提醒如果别人要求复现你的仿真结果这一步比参数表还重要否则任何一次运行都是新结果。6. 让仿真结果更有说服力的三个进阶技巧验证链路正确性不能只靠一条BER曲线。我常用的第一个技巧是加一条无编码QPSK的理论误码率曲线做对照。无编码QPSK在AWGN信道下的理论误码率是0.5 * erfc(sqrt(EbN0_lin))把这条线画在同一张图上Turbo链路的增益一眼可见一般码率1/3迭代6次在BER10^-4时能比无编码提升4到5dB。第二个技巧是观察迭代收敛行为。在2dB信噪比下分别记录迭代1、2、4、6次后的误码率如果前两次迭代误码率下降明显而后面趋缓说明译码器工作正常如果迭代到第4次仍然没有收敛趋势大概率是交织器或软信息链路有问题。这个方法比看最终BER曲线更早暴露问题也更容易定位是哪一级链路异常。第三个技巧是星座图检查。在解调器输入端和差分检测后各画一张星座图前者应该看到围绕pi/4-QPSK星座点的噪声云且没有明显的相位旋转后者应该看到旋转到标准QPSK象限的四个清晰簇。如果第二张图的簇出现45度偏移说明差分相位计算的参考方向有问题。我自己的习惯是在交付前跑一遍参数扫描表把交织器长度、迭代次数、码率三个参数的组合结果整理成小表格和主要BER图放在一起。这样看的人能直观理解每个参数的影响也不至于误以为只有一组参数能复现。最后说一个血泪教训有一次我把码率从1/3改到1/2但没同步修改接收端的噪声方差归一化导致曲线整体偏移了快2dB排查了两天才发现是信道置信度Lc的公式在换码率后没更新。从那以后我把所有参数推算过程都写成注释放在主脚本头部每次改参数先看注释再动代码。希望这些经验和坑能帮到你让你的pi/4-QPSKTurbo仿真链路一次跑通少走我走过的弯路。本文还有配套的精品资源点击获取