RS码与卷积码级联设计:原理、交织器及MATLAB仿真实现
发布时间:2026/9/11 18:45:37来源:尧图网络
简介涵盖卷积编码、RS编码、AWGN信道模拟以及二进制与伽罗华域转换的完整MATLAB实现面向通信专业学生、研究人员以及对差错控制编码感兴趣的开发者用于学习RS码、卷积码及二者级联编译码的工程化仿真。资源包共5个m文件整体约4KB文件结构轻巧各函数职责分离便于按模块调用和调试。已有773人参与学习。借助这套源码可快速搭建从信源编码、加噪传输到级联解码的仿真链路直观观察RS码对突发错误的纠正能力以及卷积码在低信噪比下的表现通过修改编码参数还能进一步比较不同级联配置的误码性能适合作为课程设计、毕业设计或通信系统仿真的起步模板。所有函数均给出参数注释便于理解RS编码中生成多项式与纠错能力t的关系以及卷积码维特比解码的回溯过程对调试网络传输或存储信道中的错误控制模块亦有参考价值。1. RS码遇上卷积码级联不是把两个编码器串起来那么简单在深空通信、卫星数传和DVB这类对误码率极其敏感的链路上单靠RS码或者单靠卷积码往往都顶不住。RS码是分组码对连续突发错误有很强的纠正能力卷积码配合维特比译码则擅长清理高斯白噪声带来的随机错误但一旦错误连成一片译码器也会迅速失锁。把两者级联起来等于把“纠突发”和“纠随机”两种能力分层使用再用交织器把残余错误打散最终误码率可以比单级编码低好几个数量级。这篇文章就围绕这条RS-卷积码级联链路展开讲清楚为什么外码用RS、内码用卷积码以及在MATLAB里怎么把rsenc、convenc、vitdec这些函数拼成一个能跑通的最小仿真链路。先说明一点这里的RS是里德-所罗门码Reed-Solomon不是数字电路里说的RS触发器阅读时把RS锁存器的知识放到一边下面整条链路只涉及纠错编码。2. 为什么外码用RS、内码用卷积码从错误特性看级联设计级联码的核心思路不是简单把两个编码器串在一起而是先想清楚每一级到底在纠正什么错误。RS码工作在符号级一个符号就等于GF(2^m)里的一个元素卷积码工作在比特级用维特比译码器在网格图上寻找最大似然路径。两者错误模型不同级联后才能各管一段。2.1 分组码与卷积码的错误模型是互补的RS(n,k)码的纠错能力用符号数衡量。比如RS(15,11)建立在GF(2^4)上每个符号4比特t(n-k)/22也就是每个码字最多能纠正2个符号错误。这2个符号错在哪个比特位置无所谓哪怕一个符号里4个比特全错也只算一个符号错误。这种特性天然适合突发错误场景信道里一阵脉冲干扰打掉几十个连续比特落到RS码眼里可能只是几个符号错了仍然能救回来。卷积码则相反。约束长度7、码率1/2的卷积码本身不按符号工作它把每个输入比特都展开成两个信道比特维特比译码器利用网格图上的约束关系把分布在若干比特间隔内的随机错误最大概率地纠正掉。但卷积码最怕长突发一段连续错误会让幸存路径在网格图上发生不可逆偏移产生成片的错误传播。把RS放在卷积码外面正是为了让卷积码先承受信道伤害把残余错误压缩成尽量短、尽量分散的突发片段再由RS码做最后一层清理。参数RS(15,11)卷积码(2,1,7)编码类型分组码卷积码处理单位GF(2^4)符号比特码率11/15 ≈ 0.7331/2MATLAB核心函数rsenc / rsdecconvenc / vitdec抗错误类型连续突发错误独立随机错误级联中的角色外码内码注表中卷积码(2,1,7)表示码率1/2、约束长度7生成多项式通常写成八进制[171 133]。2.2 交织器把一串长错拆成几个分散的错如果RS码直接接在卷积码后面卷积译码器吐出来的突发错误会集中在同一个RS码字里一旦错误符号数超过t这个码字就彻底救不回来。而交织器要解决的就是这个问题发送端把D个RS码字按行排成一个矩阵再按列读出接收端先按列填回矩阵再按行送进RS译码器。这样一段连续的错误符号经过解交织后会被平均分配到D个不同的RS码字里。用RS(15,11)举例t2。假设维特比输出了一段长度为8个符号的连续错误不解交织时这8个错误全在一个码字里RS译码直接判废加了交织深度D4之后8个错误分摊到4个码字每个码字只碰到2个错误恰好落在纠错能力边界上。所以交织深度D有一层硬约束要让每个RS码字分到的错误数不超过tD至少要大于等于“最长突发符号数/t”。实际工程里我一般会把D取得比这个下限大一些因为维特比译码的错误突发长度是随信道变化的留出余量更稳妥。2.3 MATLAB里的级联编码对象trellis、gf数组和状态转移图MATLAB通信工具箱把这两类编码都封装成了很固定的接口。卷积码用poly2trellis先生成网格结构下一步做编码译码都要引用这个结构体。写仿真时第一件事就是检查trellis是否合法trellis poly2trellis(7, [171 133]); isValid istrellis(trellis);生成多项式171八进制对应二进制1111001133对应1011011约束长度7表示当前输出与前6个输入比特相关。istrellis返回1说明这个trellis结构没问题。想看更底层的关系可以用trellis.nextStates和trellis.outputs这两个字段画出卷积码的状态转移图确认从某个状态出发、输入0或1之后的下一个状态和输出是否跟理论一致。RS码在MATLAB里的输入输出不是普通数字数组而是gf对象。rsenc要求传入的每行是k个GF(2^m)符号返回n个符号的码字rsdec则返回修正后的完整码字。这套接口从R2018a到较新的R2026b版本里调用方式没有变化但前提是安装并启用了Communications Toolbox否则会直接报“未定义函数或变量 rsenc”。3. 用MATLAB把RS-卷积码级联链路从零跑通这一章直接给出一套能运行的最小仿真。为了在代码里能看清每个环节的数据形态先用小参数RS(15,11)配合交织深度4内码用约束长度7的1/2卷积码。总码率只有(11/15)×(1/2)≈0.3667换来的是很强的纠错能力。3.1 链路参数与数据流先把一帧的数据结构定下来m4表示一个RS符号4比特k11是信息符号数n15是码字长度t2是纠错能力D4是交织深度也就是每帧处理4个RS码字。调制方式用BPSK加硬判决这样维特比译码直接用0/1序列输入便于观察级联效果。整条链路的数据流是信息比特 → 比特分组转RS符号 → RS编码 → 按列交织 → 符号转比特 → 卷积编码 → BPSK映射 → AWGN信道 → 硬判决 → 维特比译码 → 比特转符号 → 按列解交织 → RS译码 → 信息比特统计。3.2 发送端编码代码m 4; n 15; k 11; t 2; D 4; % 交织深度每帧4个RS码字 tblen 35; % 维特比回溯深度约5倍约束长度 trellis poly2trellis(7, [171 133]); R_total (k / n) * 0.5; % 级联总码率 msgBitsPerFrame k * m * D; % 4个码字每码字11个符号每符号4比特 dataBits randi([0 1], msgBitsPerFrame, 1); % 比特 - GF(2^4)符号每4比特组成一个十进制数0~15 dataSym bi2de(reshape(dataBits, m, [])., m, left-msb); % 整理成D行k列每一行是一个待编码RS信息字 dataMat reshape(dataSym, k, D).; % RS编码输出D行n列每行一个RS码字 rsSym rsenc(gf(dataMat, m), n, k); rsMat rsSym.x; % 提前把gf对象转成普通数组 % 交织D行n列矩阵按列读出轮流取4个码字的同一列符号 interSeq rsMat(:); % 符号 - 比特得到卷积编码器的输入序列 bitsMat de2bi(interSeq(:), m, left-msb); % 每行一个符号的4位比特 convIn reshape(bitsMat., [], 1); % 按行展开成连续比特流 % 卷积编码默认从全零状态开始 codedBits convenc(convIn, trellis);代码里几个容易错的地方说明一下。dataSym先由176个比特按4比特一组转成44个符号再reshape(dataSym, k, D).整理成4行11列这里的转置不能省否则RS编码时每一行的11个符号就不是原来按顺序排列的信息组。交织用rsMat(:)一步完成因为MATLAB矩阵按列存储列优先展开正好等价于“先取第1列4个符号再取第2列4个符号”也就是从4个RS码字轮流抽符号。如果此时你用reshape而不是(:)会得到一个4行15列矩阵而不是列向量后面卷积编码会直接报维度错误。比特映射那里我特意用了bitsMat.再展开保证“符号0的4个比特符号1的4个比特”按原始顺序拼接成比特流直接bitsMat(:)会得到所有符号的第1比特、第2比特……顺序全部打乱这个坑非常隐蔽解交织后RS译码结果会变成随机数。3.3 信道加噪和接收端译码代码EbN0_dB 6; EsN0_dB EbN0_dB 10*log10(R_total); % BPSK下每个符号对应一个信道比特 numBits length(codedBits); txSig 1 - 2*codedBits; % BPSK映射0-11--1 noiseVar 1 / (2 * 10^(EsN0_dB/10)); rxSig txSig sqrt(noiseVar) * randn(size(txSig)); rxBits double(rxSig 0); % 硬判决负数判为1 % 维特比译码trunc模式适合按帧处理输出和输入长度对齐 decBits vitdec(rxBits, trellis, tblen, trunc, hard); % 比特 - 符号 bitsMatRx reshape(decBits, m, []).; decSymSeq bi2de(bitsMatRx, left-msb); % 解交织把长度为D*n的符号序列按列填回D行n列矩阵 decMat reshape(decSymSeq, D, n); % RS译码输出gf对象其中x是修正后的完整码字 decRsSym rsdec(gf(decMat, m), n, k); infoSymbols decRsSym.x(:, 1:k); % 取每行前k个信息符号 % 信息符号转比特与发送端比较 outBits reshape(de2bi(infoSymbols(:), m, left-msb)., [], 1); ber sum(outBits ~ dataBits) / length(dataBits);加噪时EbN0_dB是信息比特信噪比而信道里实际传输的是编码后的比特所以要先用总码率R_total换算成EsN0_dB。BPSK信号能量归一化为1时噪声方差是这个公式注意10^(EsN0_dB/10)是线性功率比不是dB值。vitdec用trunc模式配合convenc不补尾比特的处理方式两者状态起点都是全零。tblen35时理论上帧长480比特远大于回溯深度末端截断对误码的影响可以忽略。rsdec输出的是gf对象我取.x转成普通数组再截前k列就是恢复出的信息符号。3.4 用一个小循环观察级联增益上面单帧代码调通后把整个编码-信道-译码过程包成一个函数外层循环跑多个EbN0_dB值就能看到曲线。以EbN06dB为例先看vitdec输出的残余错误再看rsdec之后是否清零。最典型的现象是不加RS时这个信噪比下卷积码已经有零星误码加完RS级联后短短几帧内误码率会直接掉到零。如果跑了几帧都是零就降低0.5dB继续测直到两条链路出现可对比的差异为止。4. 级联仿真必调的3个参数与2个容易翻车的坑级联链路里参数不是越大越好每个参数都对应一组代价。调参时先看它对哪个模块生效再配合实际误码率曲线决定方向比盲目堆参数可靠得多。4.1 维特比回溯深度tblen延迟和误码的折中回溯深度决定维特比译码器在决定某个比特时向后看的长度。取太短幸存路径还没收敛就强行判决误码明显变多取太长对误码改善很小但每帧处理延迟和内存占用线性增长。常见做法是取约束长度的5到7倍。约束长度7时35到49都可以小帧仿真用35足够。在trunc模式下帧长必须明显大于tblen否则帧尾那一段没有足够的后续路径支撑判决尾部误码率会偏高。如果发现加大tblen后BER没有变化说明当前的工作点早就收敛了问题出在其他环节。4.2 交织深度D抗突发能力和时延的取舍交织深度的作用在2.2节讲过这里讲怎么选。理论上D越大突发错误摊得越均匀但代价是帧长变为原来的D倍首字节时延也会成倍放大。MATLAB仿真里这个代价表现为矩阵规模和循环次数变大实际系统里则可能直接影响协议层重传设计。我的默认做法是先取D4把链路跑通再根据卷积译码器实际输出的错误突发长度调整。统计突发长度的方法是在某个EbN0下记录decBits里的连续错误段取95%分位点再除以RS符号比特数m得到符号级突发长度L最后选D≥ceil(L/t)。如果D选得太小会出现“RS译码器cnumerr-1”这类整体不可纠正的情况。4.3 从RS(15,11)换到RS(255,223)改3处就能升级小RS码适合调试实际链路常用RS(255,223)每符号8比特纠错能力t16。切换时只需要改三处m从4改成8n/k从15/11改成255/223交织深度D通常可以缩小到2或维持4。此时一个符号占8比特RS编码默认使用GF(2^8)的本原多项式不需要手动指定生成多项式rsenc(gf(dataMat,8),255,223)就能工作。注意每帧信息比特数变成223×8×D比特转符号时8比特一组reshape的第二个参数同步改成8。参数RS(15,11)调试版RS(255,223)工程版符号比特数m48信息符号k11223纠错符号数t216建议交织深度D42~4每帧信息比特1761784×D4.4 坑1vitdec输入位序和模式选错vitdec有trunc、cont、term三种工作模式。cont模式是流式的输出相对输入有tblen的延迟用在帧仿真里要对齐首尾非常麻烦term模式要求发送端在每帧末尾补足够的零比特让编码器回到零状态trunc模式假定每帧从零状态开始、帧尾直接截断正好对应convenc不补尾比特的默认行为。硬判决模式下输入必须是0/1序列软判决则要量化成指定比特数的整数。很多新手把cont模式用在帧数据上误码统计时没有扣除延迟导致BER曲线看起来比实际差一个量级。4.5 坑2解交织的维度写反发送端的交织是“D行n列按列读出”接收端必须是“D行n列按列填回”。如果接收端用了reshape(decSymSeq, n, D)矩阵变成n行D列每一行的符号会横跨多个原始RS码字RS译码器等于在译一组乱序数据结果往往是cnumerr全是-1或者误码率居高不下。排查方法很简单发一帧序列(0:59)的符号不发噪声直接走完整条链路打印解交织后的decMat矩阵看第1行是不是原始的第1个码字。不是的话先检查维度不要急着调信噪比和tblen。5. 不用猜链路对不对三条验证技巧和一条BER曲线级联链路环节多任何一个维度错误都会导致最终BER异常但定位起来很费时间。我习惯在正式跑曲线之前先做三个快速自检全都通过后再开始统计误码。5.1 三个能拦住80%低级错误的自检第一个自检是零噪声回环测试。把加噪那行代码的noiseVar设成0也就是rxSig txSig走完整条链路期望BER严格为零。任何一维解交织反转、比特顺序错位都会在这个测试里暴露。第二个自检用已知序列验证交织发送符号序列取mod(0:59, 15)接收端打印decMat对照发送端的rsMat逐行检查。第三个自检利用rsdec的第二个返回值它会对每个码字返回实际纠正的符号数无法纠正时返回-1[decRsSym, cnumerr] rsdec(gf(decMat, m), n, k); if any(cnumerr -1) fprintf(EbN0%.1fdB 存在不可纠正码字\n, EbN0_dB); endcnumerr是每个码字逐个统计的长度等于码字数D。一个码字返回-1说明卷积译码器吐出的错误已经超出RS纠错能力这时候优先加大交织深度而不是盲目增加发射功率。5.2 级联BER曲线怎么画才说明问题自检通过后把前面所有代码封装成runCascadeSim(EbN0_dB)函数返回值是BER。循环跑多个信噪比点EbN0_dBList 0:1:8; berList zeros(size(EbN0_dBList)); for idx 1:numel(EbN0_dBList) berList(idx) runCascadeSim(EbN0_dBList(idx)); end semilogy(EbN0_dBList, berList, o-); grid on; xlabel(Eb/N0 (dB)); ylabel(BER);对比实验建议只把RS编码和解交织去掉保留卷积码和BPSK同时把总码率改成0.5重新计算EsN0_dB再画一条纯卷积码曲线。两条曲线放在同一张图上级联码的编码增益一目了然。如果加噪时忘记用总码率换算级联曲线会整体向左偏移约4.36dB看起来“性能特别好”实际是横坐标标错了。把R_total的计算和换算公式写进注释下次回来调试就不会在横坐标上栽跟头。本文还有配套的精品资源点击获取
网站建设高端定制企业官网