简介:压缩包内含完整Turbo码编译码MATLAB代码,基于SOVA与LogMAP两种典型解码算法实现,覆盖编码、交织、迭代解码与误码率统计全流程。代码结构清晰,面向通信专业学生、研究人员及系统设计者,适合用于理解Turbo码原理、开展链路级仿真或作为课程实验素材。包内共12个文件,以.m脚本为主,覆盖SOVA解码、LogMAP解码、RSC编码、交织器及误码率统计等核心功能,另含误码率曲线图(.fig)和仿真数据(.mat),整体仅20KB,轻量易用。资源默认给出1/10码率下的仿真结果,体现纠错位与信息位的权衡;交织器设计将连续错误分散,配合迭代解码可显著降低误码率,用户可通过调整参数适应不同信道环境。目前已有302人学习,代码开放且注释明确,支持修改码率、迭代次数等关键参数,便于对照误码率曲线深入分析Turbo码性能,是教学与工程验证的实用工具。
1. 低信噪比下的Turbo码:为什么SOVA和LogMAP是解码器的关键
Turbo码可能是过去三十年在低于1dB信噪比区域最实用的信道编码方案之一。1993年Berrou等人提出时,它用迭代解码换来了逼近香农限的增益,代价是解码计算量成倍增长。turbo_new.rar这套MATLAB代码,把Turbo码的编码、交织、SOVA软输出维特比解码和LogMAP对数域后验概率解码串成了一条完整链路,包含trellis.m、rsc_encode.m、sova0.m、logmapo.m、turbo_sys_demo.m等文件,可以直接跑出误码率曲线ber1.fig,也保留了turbo_sys_demo1.mat数据文件供二次分析。
我拆这份代码的时候,最大的感受是它把两个解码算法的差异暴露得非常清楚:SOVA是维特比算法的软输出版,路径竞争通过幸存路径度量差获得置信度;LogMAP则是完整的最大后验概率递推,只是把乘法换成了对数域加法。对做通信物理层仿真的工程师来说,这套代码既能用来验证理论,也能当修改码率、迭代次数和交织器长度的起点。下面先从编码侧看起,再进入解码和仿真链路。
2. 编码侧实现:RSC编码器与交织器如何组合出Turbo码
2.1 encoderm.m 与 rsc_encode.m:递归系统卷积码的实现
Turbo码的编码器不是单个卷积编码器,而是两个递归系统卷积码(RSC)通过交织器并联。所谓“递归”是指寄存器输出会反馈到输入端,系统码是指信息位直接作为输出的一部分。常见做法是使用生成多项式矩阵例如g = [1 1 1; 1 0 1],其中第一行是前向抽头,第二行是反馈抽头,约束长度为3。
在turbo_new.rar里,encoderm.m承担的是顶层编码调度,rsc_encode.m则完成单分量编码器的比特级计算。这个文件的核心循环通常是先算反馈比特,再算校验输出,最后做寄存器移位。按住这个思路补全一份可读版本,就是下面这样:
function [out] = rsc_encode(in, g, sc) % in : 信息比特向量,1行N列 % g : 生成多项式矩阵,[前向; 反馈],例如 [1 1 1; 1 0 1] % sc : 约束长度,寄存器个数 = sc-1 K = length(in); reg = zeros(1, sc-1); out = zeros(2, K); for i = 1:K % 反馈比特:外部输入与反馈抽头模2相加 fb = mod(in(i) + sum(g(2, 2:end) .* reg), 2); % 校验比特:前向抽头与当前寄存器状态的模2和 ext = mod(in(i) * g(1,1) + sum(g(1, 2:end) .* reg), 2); out(1, i) = in(i); % 系统位直接输出 out(2, i) = ext; % 校验位输出 reg = [fb, reg(1:end-1)]; % 寄存器整体右移 end end这段代码的关键在反馈比特fb的计算:反馈多项式g(2,2:end)决定了哪些寄存器位置参与反馈,reg是当前寄存器状态,in(i)是刚进入编码器的信息位。前向输出ext则把系统输入和寄存器状态按前向多项式做线性组合。实际rsc_encode.m的写法可能更紧凑,但只要掌握了“反馈-输出-移位”这三个动作,读起来就不会卡壳。
2.2 交织器:int_state.m 怎么打散突发的连续错误
交织是Turbo码性能的另一半。如果两个分量解码器使用完全相同的数据序列,它们会在同样的位置犯同样的错,迭代就没有增益。交织器的作用是把输入序列的次序重排,让第二个分量解码器看到的错误模式不再聚集,这样两个分量解码器就能互相纠对方的“盲区”。
int_state.m文件给出的就是交织索引映射。常见做法是用伪随机交织,比如用randperm生成长度为L的置换。下面是一段等价的例子:
function [perm] = int_state(L, seed) % L : 交织长度 % seed : 随机种子,保证每次都生成同样的交织表 rng(seed); perm = randperm(L) - 1; % 转成0基索引,便于向量访问 end这里返回值perm要同时传给编码和解码两侧:编码时,第二路RSC的输入是in(perm+1);解码时,后验概率也要按perm重排后再传给第一个分量解码器。需要特别注意,交织表必须作为一个全局参数在两个分量解码器之间共享,否则解交织和再交织错位,整个迭代解码会发散,误码率直接飘高。实际调试时,建议先固定seed,这样每次仿真的交织顺序一致,问题更容易复现。
2.3 demultiplex.m 与码率调整:从1/3到1/10的取舍
turbo_sys_demo.m里的默认码率是1/10。这是个比较激进的低码率配置:每10个信道编码比特里只有1个信息位,其余9个是校验位和系统位重复。demultiplex.m负责把编码器输出的系统位和两路校验位按打孔模式(puncture pattern)重新排列成发射序列。
我在调试这类参数时,一般用一张表来管理打孔和重复的关系:
| 目标码率 | 打孔保留比特 | 每信息位发射比特 | 典型作用 |
|---|---|---|---|
| 1/3 | 系统位+两路校验全传 | 3 | 常规Turbo码,低复杂度基准 |
| 1/5 | 系统位+校验1+校验2各保留部分 | 5 | 低信噪比下约能多获得0.3-0.5dB |
| 1/10 | 系统位+校验1+校验2大量重复 | 10 | 深衰落或极低信噪比场景更稳 |
demultiplex.m里实际做的是:系统位直接进发射序列,校验位看打孔矩阵对应位置是否为1,为1则保留,否则丢弃。修改码率时,不能只改发射比特数,交织长度和迭代次数也要同步调整。否则会出现校验位分布不均,某个码段完全没有保护,误码率曲线上就会出现一个拖尾的“错误地板”。
3. 解码侧算法拆解:SOVA与LogMAP的软输出差异
3.1 trellis.m 和 bin_state.m:网格图是所有递推的地基
SOVA和LogMAP都依赖卷积码的网格图。trellis.m就是用来生成这个结构的:对每一时刻,从当前状态出发,在给定输入比特下会跳到哪个下一状态、输出什么比特。bin_state.m则是把寄存器状态写成二进制向量,方便按位匹配抽头。
比如约束长度3的RSC,状态是2个寄存器,共4个状态。trellis.m会返回三个矩阵:next_state(input, current_state)给出下一状态,output(input, current_state)给出该转移的输出比特,以及分支度量对应的参考值。很多初学者跳过这一层,直接调sova0.m和logmapo.m,结果一换参数就报维度不匹配,就是因为网格结构没有重新生成。
我建议在写解码器之前,先用一个极短序列(比如16比特)打印出trellis的转移表,人工核对一条路径的状态跳转。这个动作在排错时能节省大量时间,尤其是在修改约束长度之后。
3.2 SOVA:在幸存路径上叠加一条软信息
SOVA的核心思想是:普通Viterbi算法只保留一条幸存路径,SOVA在每次路径合并时记录幸存路径与竞争路径的度量差Δ,用这个Δ来估计该比特的可靠度。sova0.m的实现通常是两步:前向做加比选并记录路径置信度,回溯时逐比特更新软信息。
一份简化的SOVA软输出更新示意如下:
function [llr] = sova0(branch_metric, trellis) % 简化版SOVA:用竞争路径度量差生成软信息 N = size(branch_metric, 2); num_state = trellis.num_state; metric = inf(num_state, 1); metric(1) = 0; llr = zeros(N, 1); for i = 1:N for s = 1:num_state p1 = trellis.prev_state(1, s); p2 = trellis.prev_state(2, s); m1 = metric(p1) + branch_metric(p1, s, i); m2 = metric(p2) + branch_metric(p2, s, i); [metric(s), idx] = min([m1, m2]); % 度量差的绝对值就是该比特软值的候选 llr(i) = min(llr(i), abs(m1 - m2)); end end end这里的delta就是竞争路径和幸存路径的度量差。如果两个路径度量非常接近,这个位置的比特可靠性就低;相差很大,则可靠性高。SOVA的好处是复杂度只比Viterbi多一个减法,坏处是它的软输出不是严格意义上的后验概率,在迭代解码里性能会比LogMAP差一点,尤其在高迭代次数下差距会更明显。
3.3 LogMAP:前向-后向递推与对数似然比
LogMAP则换了一整套思路。它在每个时刻同时计算前向度量α和后向度量β,再合成每个比特的对数似然比:
LLR(b_i) = ln( sum(α_prev * branch * β_next, 转移输入为1) / sum(α_prev * branch * β_next, 转移输入为0) )logmapo.m里一般会用雅可比对数近似来避免直接做指数运算。下面是简化版本:
function [L] = logmapo(rec_s, rec_p, noise_var, trellis, L_a) % rec_s : 接收系统位 % rec_p : 接收校验位 % noise_var: 信道噪声方差,由Es/N0换算 % L_a : 前一解码器送来的外部信息 alpha = zeros(num_state, N); beta = zeros(num_state, N); % 前向递推 for i = 1:N for s = 1:num_state a1 = alpha(prev1, i-1) + gamma(prev1, s, i); a2 = alpha(prev2, i-1) + gamma(prev2, s, i); alpha(s, i) = max(a1, a2) + log(1 + exp(-abs(a1 - a2))); end end % 后向递推类似,得到beta % 最后把alpha、beta和分支度量合并,生成LLR end这段代码里noise_var很关键。LogMAP的分支度量γ里需要除以2σ²,σ²由信噪比决定。如果这个参数和真实信道不匹配,误码率曲线会出现平台,甚至在低信噪比下比硬判决维特比还差。SOVA对噪声方差不敏感,因为它比较的是相对度量差;LogMAP则不同,噪声方差差一个数量级,性能立刻崩。
从资源的角度看,sova0.m和logmapo.m在同一个demo里可以被替换调用,这正好方便对比两种算法的软输出分布。我在实验里看到的现象是:LogMAP在4次迭代后性能趋于收敛,SOVA需要6-8次迭代才能追平;但每次迭代SOVA的时间大约是LogMAP的一半。折衷点就落在迭代次数和时延要求之间。
4. 误码率仿真链路:turbo_sys_demo.m 的运行与参数解析
4.1 仿真主循环:从随机比特到误码统计
turbo_sys_demo.m是整个资源的入口。它按蒙特卡罗方式跑多个信噪比点,每个点生成大量随机信息块,经过编码、交织、BPSK映射、加噪、解码,最后统计误码率。主体骨架通常是这样:
% 主仿真参数 EbN0_dB = 0:0.5:3; N = 1024; % 信息块长度 iter = 5; % 迭代次数 num_frames = 200; % 每个信噪比点的帧数 for snr = 1:length(EbN0_dB) biterr_sum = 0; frame_err = 0; for f = 1:num_frames info = randi([0 1], 1, N); % 编码、调制、加噪、解码... biterr_sum = biterr_sum + sum(dec_err); frame_err = frame_err + any(dec_err); end ber(snr) = biterr_sum / (N * num_frames); fer(snr) = frame_err / num_frames; end这里EbN0_dB是每信息比特信噪比,注意Turbo码是低码率,如果直接用10*log10(1/(2*code_rate*EbN0))计算噪声方差时,code_rate必须取1/10而不是1/2,否则你会看到误码率曲线毫无变化。这是这类demo里最常见的参数错误。正确计算出噪声方差后,再把它传进logmapo.m才能得到稳定的软信息。
4.2 ber1.fig 与 turbo_sys_demo1.mat:结果文件怎么读
压缩包里的ber1.fig是已经跑好的误码率曲线,turbo_sys_demo1.mat保存了对应的数据变量。用MATLAB打开fig后,可以直接看到X轴是Eb/N0,Y轴是误码率,通常有两到三条曲线,分别是不同迭代次数或不同解码算法的结果。
如果只想在没有MATLAB的机器上复用数据,可以这样读取:
load('turbo_sys_demo1.mat'); figure; semilogy(EbN0_dB, ber, 'o-'); grid on; xlabel('Eb/N0 (dB)'); ylabel('BER');注意读取前先看清mat里面的变量名,可能是ber、EbN0_dB、iter,也可能是x、y这种通用名。用whos -file turbo_sys_demo1.mat先列出变量,比自己盲猜变量名稳妥。
4.3 误码率与误信率曲线的关系:先看趋势再看绝对数值
“误码率和误信率的大小关系图”这个检索词值得展开说一句。误比特率(BER)统计的是错误比特占总传输比特的比例,误信率(这里一般指误帧率FER)统计的是至少含一个错误比特的帧占所有帧的比例。在信道编码评估中,FER下降得比BER慢,因为只要帧里有一个比特错,整帧就算错了。用同一个数据集画出两条曲线时,FER曲线会整体高于BER曲线,差距随信噪比增大而扩大。
| 曲线类型 | 统计口径 | 与系统设计的关系 |
|---|---|---|
| BER | 错误比特 / 总比特 | 决定业务数据质量 |
| FER | 错误帧 / 总帧 | 决定是否需要重传(ARQ) |
在实际调试中,如果BER下降但FER持平,说明解码器能纠正大多数错比特,但仍有少量帧完全解不开,这时需要增加迭代次数而不是调整交织器。如果FER下降但BER不明显,说明重传率在降低,对系统吞吐量利好。
5. 调整迭代次数与码率时的三个典型坑
5.1 迭代次数不是越多越好
配置迭代次数时,迭代增加,增益递减,并且迭代解码会放大外部信息的相关性。我在跑这套代码时发现,超过8次迭代,误码率曲线基本不再下降,反而因为数值误差出现轻微上翘。简单判断标准是:看相邻两次迭代的LLR差,如果小于0.01,继续迭代已经没有意义。sova0.m的收敛速度比logmapo.m慢,所以用SOVA时迭代次数建议设6到10,LogMAP设4到6。
5.2 改码率时同步改交织器长度和打孔表
把1/10改成1/5不是简单删一半重复比特。交织器长度必须仍然等于信息块长度,但打孔模式里的校验位保留位置要重新设计。我踩过的坑是只改了demultiplex.m的输出长度,没有更新噪声方差里的code_rate,导致误码率曲线在低信噪比反而变差。建议把码率定义为全局常量,编码、交织、解调、解码所有模块都引用同一个变量,避免一处漏改。
5.3 验证两套解码器结果是否一致
快速验证方法是:在无噪声情况下用logmapo.m解一个全零码块,LLR的符号应为负且绝对值大;再对同一个接收序列分别跑sova0.m和logmapo.m,统计两者硬判决的差异位置。如果差异集中在SOVA不可靠的比特位置,也就是LLR绝对值小于0.5的位置,就说明路径度量差逻辑正确;如果差异点是散乱的,优先检查trellis.m的网格状态编号是否一致。
拿到这套代码之后,先用默认参数复现ber1.fig,再每轮只改一个变量,记录误码率曲线移动了多少。这样几个参数跑下来,Turbo码的软信息迭代特性会比任何教科书上的图都直观。
本文还有配套的精品资源,点击获取