拓冰建站拓冰建站
首页 / 资讯中心 / 正文

(2,1,7)卷积码:从生成多项式到维特比译码与Simulink仿真

简介面向通信工程、电子信息类专业学生与初学信道编码的工程师这份文档围绕MATLAB/Simulink环境下卷积编码器的设计与仿真展开可用于课程设计、毕业设计选题参考以及自学纠错编码原理。全文从分组码与卷积码的区别切入梳理卷积编码原理、编码算法与结构描述方法说明约束长度、码率、生成多项式对纠错性能的影响并给出2,1,2、2,1,3等典型编码器的推导过程。第二章以MATLAB程序与Simulink模块图实现建模观察编码后码流结合最小距离与误码率曲线分析性能并延伸到Viterbi译码的配合使用思路。压缩包为doc格式仅1个文件约365KB体积轻巧可直接作为理论推导与仿真实验的报告底稿。目前已有592人学习下载适合希望把编码原理快速落到仿真验证的读者。1. 同样码率 1/2(2,1,7) 卷积码为什么能比分组码省下几个 dB做链路预算的时候经常撞到这种局面调制方式已经定死 BPSK发射功率不能再加误码率指标还差一截唯一能动的是信道编码。分组码的思路是把 k 个信息位攒够一组再算校验位组与组之间互不相关译码必须等整组收齐码长 n 一大缓冲和延时跟着涨卷积码反过来k 和 n 取得很小常见的就是 (2,1,K)每来一个信息比特立刻吐出两个码元输出不仅和当前比特有关还和前面 K-1 个比特有关。这个「有关」就是约束长度也是纠错能力的来源。约束长度每增加 2自由距离 d_free 大概往上抬 23误码率曲线在同一个 Eb/N0 下会明显往下压。代价是编码器移位寄存器级数变多维特比译码的状态数按 2^(K-1) 指数增长K7 时是 64 个状态K9 就跳到 256 个译码器的分支度量运算量和存储量跟着翻倍。下面按「生成多项式 → 状态转移表 → MATLAB 编码 → Simulink 链路 → 参数取舍」推一遍每一段都尽量落到能跑起来的代码和能改的参数上。适合做通信原理课程设计、写 FPGA 编码器逻辑、或者在链路仿真里需要一张可对比误码率曲线的人。2. 生成多项式、状态转移表与网格图把编码器写成一个有限状态机2.1 冲击响应视角约束长度约束了什么先拿最简单的 (2,1,3) 编码器做解剖。它有两级移位寄存器 D1、D2一个输入比特 m两个模 2 加法器输出 c1、c2。生成多项式写作 g1 1 D D²g2 1 D²意思是 c1 由「当前输入、上一拍输入、上上拍输入」三者异或得到c2 由「当前输入、上上拍输入」异或得到。判断约束长度的一个笨办法但很好用往编码器里塞一个孤立的 1其余全塞 0看这个 1 能在输出端「影响」几个时刻。M 个输出时刻后就彻底消失说明寄存器最多记 K-1 2 个历史比特约束长度 K 3编码后相互关联的码元总数是 n·K 6。% 冲击响应实验(2,1,3) 编码器输入单个 1 后跟一串 0 g1 [1 1 1]; g2 [1 0 1]; % 对应八进制 7 和 5第 1 位是当前输入 reg [0 0]; % 两级寄存器初始全零 out []; for m [1 0 0 0 0] buf [m reg]; % 拼成 1x3 的窗口 out [out mod(sum(buf.*g1),2) mod(sum(buf.*g2),2)]; %#okAGROW reg [m reg(1:end-1)]; % 右移丢掉最老的比特 end disp(out) % 1 1 0 1 1 1 0 0 0 0这段代码里的buf就是抽头窗口g1/g2中为 1 的位置表示该位参与异或。输出前三个时刻分别是 11、01、11三个时刻之后就全是 0——冲击响应的长度正好等于 K。把任意输入序列看成若干个移位后的冲击响应叠加异或起来就是编码输出这就是「卷积」这个名字的来历。2.2 八进制生成多项式的读法和抽头对应关系工程上不会把抽头写成 1DD²而是八进制。171 这个数转成二进制是 1111001从右往左数第 1 位对应当前输入比特依次往左对应 D¹、D²……D⁶K7 时一共 7 位正好覆盖 6 级寄存器加当前输入。133 转成二进制是 1101011抽头接在 1、2、4、6、7 位上。八进制二进制K 位K含义71113当前输入、D¹、D² 全参与51013当前输入和 D² 参与17111110017六条支路异或典型前向抽头13311010117反馈感更强的抽头组合5611011100019K9 常用好码之一把八进制串转成抽头向量没有必要手算一行就够function taps oct2taps(octstr, K) % 八进制生成多项式字符串 - 1xK 抽头向量 % 输出第 1 位对应当前输入第 K 位对应最早的寄存器位 taps dec2bin(base2dec(octstr, 8), K) - 0; end % 用法 oct2taps(171, 7) % 1 1 1 1 0 0 1 oct2taps(133, 7) % 1 1 0 1 0 1 1base2dec(...,8)负责八进制转十进制dec2bin(...,K)补足 K 位减字符0把 ASCII 字符变成 0/1 数值。这个函数在做码搜索和参数批量扫描时比查表靠谱得多。2.3 状态转移表手推 (2,1,3) 的四个状态把两级寄存器 D1D2 的取值当成状态就有 a00、b01、c10、d11 四种。每来一个输入比特寄存器右移一格D1 更新为当前输入D2 更新为原来的 D1同时拍出两个码元。定义 c1 m ⊕ D1 ⊕ D2c2 m ⊕ D2可以把四个状态的两条出边全部列出来当前状态输入 m下一状态输出 c1c2a (00)0a00a (00)1c11b (01)0a11b (01)1c00c (10)0b10c (10)1d01d (11)0b01d (11)1d10这张表就是编码器的完整行为描述。拿输入序列 101101 从状态 a 出发走一遍1 → c输出 110 → b输出 101 → c输出 001 → d输出 010 → b输出 011 → c输出 00码流是 11 10 00 01 01 00。不同教材对寄存器编号方向的定义不一致同一串输入可能整体位移一位核对时以工具箱输出为准别硬扛。注意手推状态表时最容易错的是状态编号顺序。D1 是最近一次输入还是最老一次输入直接决定表格长什么样改动之前先确认自己的约定。2.4 从网格图到自由距离把状态表沿时间轴铺开就成了网格图每个时刻一列状态点连线代表转移线上的标注是输出码元。维特比译码要干的事就是在这张网格里找一条与接收序列汉明距离硬判决或欧氏距离软判决累计最小的路径。衡量一个卷积码好坏的核心指标是自由距离 d_free即从全零路径出发再回到全零路径的所有非零路径中最小的重量。它决定了渐近误码率随 Eb/N0 下降的斜率也决定能纠几个错硬判决下大致能纠 (d_free-1)/2 个比特错误。常见好码的自由距离大致是码型生成多项式八进制d_free状态数(2,1,3)7, 554(2,1,5)23, 35716(2,1,7)171, 1331064(2,1,9)561, 75312256K 从 7 涨到 9d_free 只从 10 涨到 12但状态数翻了四倍。这就是为什么实际系统里 K7 用得最多——增益和复杂度在这个点上比较划算。3. MATLAB 编码实现poly2trellis、convenc 与手写移位寄存器3.1 用 poly2trellis 生成网格描述MATLAB 里描述卷积码的标准结构叫 trellis字段包括 numInputSymbols、numOutputSymbols、numStates、nextStates 和 outputs。手填这五个字段既烦又容易错poly2trellis可以把生成多项式直接翻译过去K 7; % 约束长度 trellis poly2trellis(K, [171 133]); % 生成多项式八进制写字符串或十进制都行 disp(trellis.numStates) % 64poly2trellis的第一个参数是约束长度第二个参数是生成多项式数组元素个数等于 n这里是 2。八进制必须写成171这种字符串形式写成数字 171 会被当成十进制算错。如果生成多项式对应的寄存器级数不足 K-1函数会直接报错这算是免费的自检。3.2 convenc 一行完成编码尾比特不能忘rng(0); msg randi([0 1], 1, 200); % 200 个随机信息比特 msg_pad [msg zeros(1, K-1)]; % 末尾补 K-1 个零把状态逼回全零 code convenc(msg_pad, trellis); % 输出 2*(2006) 412 个码元convenc只做编码不认识「帧结束」这件事。如果不补尾比特译码端最后一小段的状态是未知的维特比译码在这一段的路径选择会失去约束每帧末尾几个比特错误率明显偏高整条 BER 曲线会卡在一个错误地板上不往下走。补零之后状态归零译码端可以从已知的终止状态回溯这段损失就消失了。代价是额外传了 K-1 个冗余比特码率从 1/2 略微下降到 200/412 ≈ 0.485。信息帧很短的时候这个开销不能忽略所以短包系统里常见另一种做法不补零译码端用 Truncated 模式按固定回溯长度强行判决。3.3 手写移位寄存器版本和硬件逻辑一一对应工具箱函数跑得再顺写 FPGA 或者写 C 的时候还是得自己实现一遍。下面这个函数按硬件的数据流写reg就是那排移位寄存器function code conv_encode_2_1_K(msg, g1, g2, K) % (2,1,K) 卷积编码逐比特推进的移位寄存器模型 % msg : 1xN 信息序列含尾比特 % g1,g2: 长度 K 的抽头向量第 1 位对应当前输入 % code : 1x2N 编码输出c1 c2 交替排列 reg zeros(1, K-1); % K-1 级寄存器初始全零 code zeros(1, 2*numel(msg)); idx 1; for i 1:numel(msg) buf [msg(i) reg]; % 当前输入 历史状态 c1 mod(sum(buf .* g1), 2); % 模 2 加等价于异或 c2 mod(sum(buf .* g2), 2); code(idx:idx1) [c1 c2]; idx idx 2; reg [msg(i) reg(1:end-1)]; % 右移一位丢弃最老比特 end end三个地方值得盯住buf的长度必须是 K多了少了sum(buf.*g1)都会算错mod(...,2)是异或的数学等价形式换成xor链也行但写法更啰嗦reg的更新必须在计算输出之后顺序颠倒就等于提前把当前比特移进了寄存器输出整体错一格。和工具箱对拍一下K 3; msg [1 0 1 1 0 1 0 0 0]; % 末尾自带 2 个尾比特 mine conv_encode_2_1_K(msg, [1 1 1], [1 0 1], K); trellis poly2trellis(3, [7 5]); ref convenc(msg, trellis); isequal(mine, ref) % 返回 1 才算通过对拍不通过时先看长度conv_encode_2_1_K输出 2N 个码元convenc在输入已含尾比特的情况下输出也是 2N 个。长度一致再逐位比前面几位就错通常是抽头顺序反了只有末尾几位错基本是尾比特数量不对。3.4 矩阵法和移位寄存器法的差别通信原理教材里还有一套基于生成矩阵 G 的写法MATLAB 里对应的是把生成矩阵和输入序列做模 2 乘function output cnv_encd(g, k0, input) % g : 生成矩阵行数 输出端口数 n0列数 l*k0l 为约束长度 % k0 : 每拍输入比特数 % input: 输入信息序列 if rem(length(input), k0) 0 % 长度不是 k0 整数倍就补零 input [input, zeros(1, k0 - rem(length(input), k0))]; end n length(input) / k0; l size(g, 2) / k0; % 约束长度 n0 size(g, 1); % 输出端口数 u [zeros(1,(l-1)*k0), input, zeros(1,(l-1)*k0)]; % 首尾补零状态从零出发回到零 ul u(l*k0:-1:1); for i 1:nl-2 ul [ul, u((il)*k0:-1:(i*k01))]; % 反向滑窗取 l 组 end uu reshape(ul, l*k0, nl-1); % 重排成矩阵每列一拍 output reshape(rem(g*uu, 2), 1, n0*(ln-1)); % 模 2 乘 展平 end这段代码的巧妙之处在于把「移位」这个时序动作压成了矩阵乘法uu的每一列是同一时刻所有寄存器里的值g*uu一次性算出所有时刻的输出。rem(g*uu,2)是逐元素取模 2对应硬件里的异或门阵列。矩阵法在批量处理长序列时比 for 循环快不少但它不适合直接映射到硬件而且中间变量ul的构造依赖l*k0这个长度k0 变大之后索引很容易写错。仿真验证用矩阵法硬件实现参考移位寄存器版本是比较省事的组合。4. Simulink 链路搭建编码—AWGN—维特比译码与误码率扫描4.1 模块清单与连线顺序Simulink 里跑通一条卷积码链路不需要多少模块关键是把维特比译码器的参数和编码器对齐Bernoulli Binary Generator - Convolutional Encoder - BPSK Modulator Baseband - AWGN Channel - BPSK Demodulator Baseband - Viterbi Decoder - Error Rate Calculation - Display误码率统计需要一个参考支路从 Bernoulli Binary Generator 直接拉一根线到 Error Rate Calculation 的 Tx 端口Rx 端口接维特比译码的输出。如果 AWGN 已经用 Eb/No 模式设置BPSK 调制解调这两个模块可以省掉让 AWGN 直接工作在比特域仿真速度会快很多。4.2 关键参数表模块参数名建议取值说明Bernoulli Binary GeneratorSample time1/1000决定每帧比特数Convolutional EncoderTrellis structurepoly2trellis(7,[171 133])与译码器必须完全一致Operation modeTerminated结尾补 K-1 个零状态归零AWGN ChannelEb/No (dB)0 到 8扫曲线时用变量EbNo驱动Number of bits per symbol1BPSK 对应 1Input signal power1与 BPSK 输出功率匹配Viterbi DecoderTrellis structure同编码器改一个必须同步改另一个Decision typeHard decision想试软判决选 UnquantizedTraceback depth35取 5·KK7 时为 35Error Rate CalculationReceive delay0Terminated 模式补零方案下译码输出与输入对齐Traceback depth是维特比译码里最需要调的一个参数。它决定译码器回溯多少步才输出判决比特取值太小误码率降不下去取值太大只是白白增加延迟超过 5K 之后曲线基本不再变化。4.3 用脚本批量扫 Eb/N0手动改一个参数点一次运行扫六条曲线要点几十次。用set_param加sim循环更省事model convcode_link; load_system(model); EbNo 0:1:8; ber zeros(size(EbNo)); for k 1:numel(EbNo) set_param([model /AWGN Channel], EbNo, num2str(EbNo(k))); set_param([model /AWGN Channel], EsNodB, num2str(EbNo(k))); simOut sim(model, StopTime, 0.1); % 每点跑固定时长 ber(k) simOut.get(ber_out); % To Workspace 变量名 end semilogy(EbNo, ber, -o); grid on; xlabel(E_b/N_0 (dB)); ylabel(BER);set_param的第一个参数是模块的完整路径模型层级深的时候路径写错会直接报找不到模块。sim返回的对象取变量的方式取决于你在模型里用的是 To Workspace 还是logsout前者用get(变量名)后者要遍历simOut.logsout。噪声是大数定律游戏Eb/N0 高的时候单次短仿真可能一个错误都统计不到曲线尾部会出现断点把StopTime调大或者固定错误数停止才是正确做法。4.4 三个常见报错的处理思路误码率恒为 0.5几乎一定是发送和接收的比特没对齐。Terminated 模式补了尾比特但误码统计没有设置对应的延迟或者译码器输出的是硬判决 0/1 而统计模块期望的是 ±1。误码率随 Eb/N0 完全不变编码器和译码器用了两套不同的 trellis。常见于改了编码器的生成多项式却忘了同步译码器或者一边写171字符串、另一边写 171 数字。每帧固定错最后几个比特译码器选了 Truncated 模式但输入没有补零或者回溯深度小于 K末尾状态还没收敛就被强行判决。把 Operation mode 改成 Terminated或者把回溯深度提到 5K 以上。提示模型跑通之后如果打算把编码器搬到 FPGA可以用 Embedded Coder 对编码器子系统做 C 代码生成把手写移位寄存器版本和自动生成的版本逐位对拍比对着波形手算快得多。5. 参数取舍回溯长度、打孔码率和生成多项式搜索维特比译码的回溯长度 τ 和误码率的关系不是线性的。τ 从 5 涨到 20BER 明显下降τ 超过 5NK7 时是 35之后曲线基本贴合再加大只增加译码延迟。下面这张表是 K7、Eb/N04 dB 附近常见的趋势实际数值会随仿真长度浮动回溯长度 τ相对译码延迟误码率趋势10低明显偏高有错误地板25中接近收敛35 (5N)较高基本收敛50高无明显改善工程上取 τ 5N 是默认选择只有对延迟极敏感的场景才会往下压压到 3N 以下要重新评估误码率指标。码率调整靠打孔。母码 (2,1,7) 的码率是 1/2带宽吃紧时按固定图案删掉一部分校验位就能提到 2/3 甚至 3/4trellis poly2trellis(7, [171 133]); puncpat [1 1 0 1 0 1]; % 6 位窗口保留 4 位删掉第 3、5 位 msg randi([0 1], 1, 300); code convenc([msg zeros(1,6)], trellis, puncpat); % 长度约为输入的 2/3 decoded vitdec(code, trellis, 35, term, hard, puncpat);puncpat里 1 表示保留、0 表示删除译码端的图案必须和编码端逐位一致差一位整条链路的输出就全乱。打孔换来带宽代价是 d_free 下降、抗噪能力变差1/2 打到 3/4 通常要损失 12 dB 的编码增益。生成多项式可以直接暴力搜。约束长度 K 固定后候选就是 2^(K-1) 到 2^K-1 之间的奇数首位必须为 1否则当前输入不参与编码穷举一遍用距离谱打分K 7; best struct(dfree, 0, g, []); for g1 2^(K-1)1 : 2 : 2^K-1 for g2 2^(K-1)1 : 2 : 2^K-1 trellis poly2trellis(K, [g1 g2]); ds distspec(trellis, 4); % 距离谱返回最小距离等信息 if ds.dfree best.dfree best.dfree ds.dfree; best.g [g1 g2]; end end end fprintf(最优: %o %o, d_free %d\n, best.g(1), best.g(2), best.dfree);步长取 2 是为了只遍历奇数distspec的第二个参数是搜索深度给小了可能搜不到真正的最小重量路径。这套穷举不需要 Optimization Toolbox标准通信工具箱就能跑K7 时双层循环大概几千次几秒钟出结果搜出来的典型解就是 171 和 133 这一对。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门