首页/新闻资讯/正文详情

卷积码与Viterbi译码MATLAB仿真及误码率排查

发布时间:2026/11/3 10:53:23 来源:云帆数科 栏目:资讯中心
卷积码与Viterbi译码MATLAB仿真及误码率排查
简介这份文档围绕卷积码编译码算法研究与实现展开面向学习数字通信、信道编码及维特比译码的本科生与工程入门者可用于课程设计、毕业设计选题参考和MATLAB仿真复现。包内仅含1个doc文件压缩包约991KB主体为排版完整的论文类文档涵盖纠错码基础、卷积码编码与译码原理、Viterbi算法流程及仿真分析章节。资源已有349人学习价值在于把码率、约束长度、回溯深度以及硬判决/软判决对误码率的影响放在同一仿真框架下比较并给出参数选择与性能优化结论读者可据此理解编码器记忆、路径度量、回溯搜索等关键概念也可借鉴其MATLAB实验设计、结果对比和论文组织结构适合需要快速建立知识框架或查漏补缺的中初级读者。1. 一条下不去的误码率曲线卷积码与 Viterbi 译码到底解决什么问题前阵子帮人看一份链路级仿真脚本加了卷积码之后误码率曲线在 1e-3 附近就躺平了信噪比从 4 dB 加到 10 dB曲线纹丝不动。查了两天不是信道模型的问题也不是调制映射的问题是 Viterbi 译码的回溯深度只给了 8而约束长度是 7。回溯深度比约束长度还短等于把刚攒起来的路径信息又扔了。这个坑很典型。卷积码编译码的原理本身不难难的是参数之间那点耦合关系——码率、约束长度、回溯深度、判决方式任何一个选错仿真出来的结果都会骗你而且骗得很有说服力曲线看着平滑、单调、像那么回事。这份资源是一套完整的卷积码编译码算法研究与 MATLAB 仿真实现从编码器的移位寄存器一路做到 Viterbi 译码的回溯判决再用误码率曲线把不同码率、不同约束长度、不同回溯深度、不同判决方式的影响量化出来。论文正文把原理、算法、仿真结论都写全了代码侧能把整条链路跑通。适合正在做通信方向课程设计或毕业设计的人也适合手上有个链路想加纠错、又不想从头翻教科书的开发者。2. 工程结构拆解一条链路级仿真由哪几段拼起来2.1 目录结构五个部分各管什么这套东西的组织方式很朴素但边界划得清楚。它不是一个大脚本从头跑到尾而是把编码、信道、译码、统计拆成独立函数主脚本只负责串流程和画图。这么拆的好处是改一个参数不用动其他文件坏处是第一次看得先搞清楚调用关系。conv_code_sim/ ├── main_sim.m % 主仿真脚本参数配置 流程串联 画 BER 曲线 ├── encoder/ │ ├── conv_encode.m % 卷积码编码器(n,k,N) 通用实现 │ └── build_trellis.m % 由生成多项式生成网格图状态转移表 ├── channel/ │ ├── bpsk_mod.m % BPSK 映射0 - -1, 1 - 1 │ └── awgn_channel.m % 加高斯白噪声按 Eb/N0 换算噪声方差 ├── decoder/ │ ├── viterbi_hard.m % 硬判决 Viterbi汉明距离作分支度量 │ └── viterbi_soft.m % 软判决 Viterbi欧氏距离平方作分支度量 ├── utils/ │ ├── ber_count.m % 误码率统计返回误比特数与总比特数 │ └── plot_ber.m % 半对数坐标画曲线支持多条对比 └── result/ % 仿真结果图与数据落盘encoder/和decoder/对称放置是有意的编码和解码共用同一张网格图build_trellis.m生成的结构体同时被两边引用。这样改码型的时候只需要改一次生成多项式编码器和译码器自动同步不会出现「编码按 (2,1,7)、译码按 (2,1,3)」这种对不上的情况。utils/ber_count.m单独拎出来也值得说一句。很多人的仿真脚本把误码统计写在主循环里一行sum(dec ~ info)/length(info)了事看着省事实际有个隐患短帧仿真时误码数太少统计出来的误码率抖动极大曲线画出来像心电图。单独成函数之后可以顺手加一个「累计误比特数达到阈值就提前结束」的保护。2.2 一条仿真链路的五段式数据流整条链路是标准的五段式信源产生随机比特编码器加冗余BPSK 映射成 ±1 符号AWGN 信道叠噪声Viterbi 译码还原最后比对原始比特算误码率。主脚本的骨架大概长这样% main_sim.m —— 单次仿真流程骨架 clear; clc; close all; % 1. 参数配置 N 3; % 约束长度 g [7 5]; % 生成多项式八进制表示(1,1,1) 和 (1,0,1) rate 1/2; % 码率 k/n K 10000; % 信息比特数 tb 20; % 回溯深度 ebno 0:1:8; % Eb/N0 扫描范围 (dB) nIter 20; % 每个信噪比点的重复次数 trellis build_trellis(g, N); % 网格图编解码共用 berHard zeros(size(ebno)); for i 1:length(ebno) errCnt 0; bitCnt 0; for it 1:nIter % 2. 信源 info randi([0 1], 1, K); % 3. 编码末尾补 m 个零比特把寄存器冲干净 m N - 1; coded conv_encode([info, zeros(1, m)], g, N); % 4. 调制 信道 tx bpsk_mod(coded); % 0--1, 1-1 snr ebno(i) 10*log10(2*rate); % Eb/N0 - 符号 SNR rx awgn_channel(tx, snr); % 5. 译码 统计 rxHard double(rx 0); % 硬判决负值判 1 dec viterbi_hard(rxHard, trellis, tb); dec dec(1:K); % 截掉补的收尾比特 errCnt errCnt sum(dec ~ info); bitCnt bitCnt K; end berHard(i) errCnt / bitCnt; end plot_ber(ebno, {berHard}, {硬判决 Viterbi});这里有几个必须交代清楚的点否则跑出来的结果会莫名其妙。第一收尾比特。卷积码的编码器是有记忆的最后一组信息的输出还没完全放出来状态也不在零态。标准做法是在信息序列尾部补m N-1个零比特让寄存器彻底清零译码后再把这部分截掉。补零会让码率略微下降实际码率变成K/(Km)*rateK 足够大时可以忽略但仿真里 K 只有几千的时候这个偏差会影响和理论曲线的对齐。第二Eb/N0到符号域信噪比的换算。awgn函数吃的是符号信噪比而论文里横轴通常写 Eb/N0两者的换算是SNR Eb/N0 10*log10(k*R)BPSK 条件下k1R是码率。这个3dB左右的常数如果漏了整条曲线会整体右移看起来「性能变差了」其实是横轴没对齐。第三每个信噪比点重复 20 次取平均。单次 10000 比特在高信噪比下可能一个错都没有误码率为 0 在半对数坐标上画不出来。重复多次累计误码数才能在 1e-5 这个量级上还有统计意义。2.3 参数在代码里的落点一张表说清四个核心参数分别影响什么、改哪个变量、预期结果怎么变这张表我一般贴在脚本开头省得每次都要回忆参数代码变量物理含义增大后的影响码率 R k/nrate每 k 比特信息产生 n 比特码字码率越高冗余越少纠错能力下降同信噪比下误码率高约束长度 NN编码器记忆深度状态数 2^(N-1)纠错能力增强状态数指数增长译码复杂度和延迟同步上升回溯深度tbViterbi 回溯保留的路径长度太短则性能急剧恶化一般取 5N 以上趋于饱和判决方式viterbi_hard/viterbi_soft输入是 0/1 还是量化软值软判决通常带来 2 dB 左右的增益但需要信道侧提供可靠度信息表格里最容易被忽略的是回溯深度。码率和约束长度大家都熟教科书公式明明白白唯独回溯深度是工程参数书上往往一句「一般取约束长度的 5 倍」带过。但对短帧、低时延场景取不到 5 倍是常态这时候性能损失有多大得自己扫出来。提示build_trellis的输出结构体同时被编解码使用改码型时只改g和N其他地方不要动。3. 编码器实现生成多项式怎么变成网格图3.1 生成多项式与移位寄存器三个比特怎么变成两个输出卷积码编码器的物理形态就是一个移位寄存器加几个模二加法器。以 (2,1,3) 码为例约束长度 N3记忆级数 m2寄存器里存着当前输入和前两个历史输入共 3 个比特。两个子生成元决定这两个输出分别从哪几个寄存器抽头g1 (1,1,1)八进制写作 7意思是三个寄存器全都参与第一个输出的模二加g2 (1,0,1)八进制写作 5意思是第 1 个和第 3 个参与第二个输出。所以每来一个信息比特会同时产生 2 个输出比特码率 1/2。那个「八进制」的写法是通信领域的习惯因为生成多项式本质是二进制系数向量写成八进制正好三个比特一组读起来紧凑。注意八进制转二进制时位序问题是最容易翻车的地方。dec2bin(7, 3)得到的是111看似没问题但dec2bin(5, 3)得101而有人习惯性写成[1 0 1]的反序[1 0 1]——恰好这个对称看不出问题。换成 6110就会暴露到底是[1 1 0]还是[0 1 1]取决于你把当前输入放在最高位还是最低位。3.2 状态转移与网格图4 个状态、8 条分支N3 的编码器有 m2 个寄存器2^2 4 个状态。每个状态在来一个输入比特后有两条出边所以网格图每个时刻有 4 个状态、8 条分支。把这 8 条分支的输出和去向事先算好存成表译码时就不用每次现算这是 Viterbi 能跑快的前提。function trellis build_trellis(g, N) % 由生成多项式构建网格图 % g: n 行 N 列的生成多项式矩阵每行一个子生成元 % N: 约束长度 m N - 1; % 寄存器级数 n size(g, 1); % 每个时刻输出比特数 S 2^m; % 状态总数 trellis.nextState zeros(S, 2); % nextState(当前状态1, 输入1) trellis.output zeros(S, 2, n); % output(当前状态1, 输入1, 输出序号) trellis.numStates S; trellis.n n; trellis.N N; for s 0:S-1 % 状态编号转寄存器内容left-msb 保证最新比特在最高位 reg de2bi(s, m, left-msb); if m 0, reg []; end for u 0:1 buf [u, reg]; % 当前输入 历史输入 for j 1:n % 模 2 内积抽头系数与寄存器内容逐位相乘求和 trellis.output(s1, u1, j) mod(sum(buf .* g(j, :)), 2); end % 状态转移新输入进最高位旧内容右移丢弃最低位 trellis.nextState(s1, u1) bi2de([u, reg(1:m-1)], left-msb); end end end参数说明g的每一行是一个子生成元长度必须等于 N否则内积对不上de2bi的left-msb选项不能省默认是右高位会导致状态和寄存器内容反序进而让译码器在错误的网格上搜索。trellis.output用三维数组存第三维是输出序号这样squeeze(output(s,u,:))就能拿到当前分支的完整 n 比特输出比用 cell 数组省事。这个函数是编解码共用的写完先自测一遍从状态 0 输入比特 1输出应该是[1 1]下一状态应该是 2。手算验证一两个分支比后面调试半天强。3.3 编码函数的逐段实现有了网格图编码其实可以不查表、直接按抽头算这样代码更直观也方便和教科书公式对照function coded conv_encode(info, g, N) % 通用 (n,k,N) 卷积码编码k1 版本 % info: 1 x L 信息比特行向量 % g: n x N 生成多项式矩阵 % N: 约束长度 m N - 1; n size(g, 1); L length(info); reg zeros(1, m); % 移位寄存器初始全零 coded zeros(1, n*L); % 预分配避免循环内扩容 for t 1:L buf [info(t), reg]; % 当前输入置最高位历史输入在后 for j 1:n % 第 j 个子生成元对应的输出放在该时刻的第 j 个位置 coded((t-1)*n j) mod(sum(buf .* g(j, :)), 2); end % 寄存器右移一位新输入进入最高位 reg [info(t), reg(1:m-1)]; end end分段说明reg是长度为 m 的行向量初始化为全零代表编码器从零态开始循环里先拼出buf长度正好是 N与生成多项式长度一致sum(buf .* g(j,:))就是模二加的结果输出按(t-1)*n j的位置写入保证同一时刻的 n 个比特连续存放最后一行更新寄存器reg(1:m-1)取前 m-1 个元素配合新输入完成右移。有一个容易忽略的边界m0 的情况N1无记忆reg(1:m-1)会变成reg(1:0)MATLAB 里返回空[info(t), []]得到单元素向量恰好也对。所以这段代码不用特判 N1。3.4 用 poly2trellis 交叉验证别信自己的第一版自己写的编码器一定要和 MATLAB 通信工具箱的标准实现对齐一次。poly2trellis、convenc、vitdec这一套是经过大量工程验证的拿来当基准最省心% 交叉验证自己写的编码器 vs 工具箱 convenc N 7; gOctal [171 133]; % (2,1,7) 标准码八进制 trellisRef poly2trellis(N, gOctal); info randi([0 1], 1, 200); m N - 1; % 工具箱版本 codedRef convenc([info, zeros(1,m)], trellisRef); % 自己写的版本先把八进制转成二进制系数矩阵 gBin zeros(2, N); for j 1:2 gBin(j, :) de2bi(oct2dec(gOctal(j)), N, left-msb); end codedMine conv_encode([info, zeros(1,m)], gBin, N); assert(isequal(codedRef, codedMine), 编码结果与工具箱不一致先查位序);这段代码的价值不在跑通在于「不一致时能立刻定位」。如果 assert 挂了八成是两个原因八进制转二进制时位序反了或者生成多项式矩阵的行对应关系搞混了第一行应该对应第一个输出比特。把oct2dec和de2bi拆开写就是为了让位序在代码里显式暴露出来而不是藏在一个base2dec里。对齐之后网格图也可以顺手验证用同一个trellisRef和自建的trellis分别对同一段随机序列做编码输出相同则说明build_trellis的状态编号、转移关系、输出表全部一致。4. Viterbi 译码核心加比选、路径度量与回溯4.1 分支度量硬判决数不同位软判决算欧氏距离Viterbi 的本质是在网格图上找一条最大似然路径。最大似然等价于最小欧氏距离而距离是可以逐段累加的这就是它能用动态规划求解的原因。每个时刻、每条分支算出来的「这一段有多像接收信号」叫分支度量。硬判决下接收端已经把模拟量压成了 0/1能用的信息只有「不同位的个数」也就是汉明距离% 硬判决分支度量接收比特与分支期望输出逐位比较 bm sum(expected ~ received); % expected、received 均为 0/1 行向量软判决下接收端保留模拟值分支度量换成欧氏距离平方% 软判决分支度量期望输出映射为 ±1 后与软值求平方误差 expectedSigned 1 - 2*expected; % 0 - 1, 1 - -1 bm sum((expectedSigned - received).^2);两种度量差的不只是一个公式。硬判决把接收信号的幅度信息扔了只留符号软判决保留了「这个比特有多像 0」的置信度在低信噪比下能多榨出 2 dB 左右的增益。代价是信道侧要能把软值送上来中间不能有硬判决环节实际系统里需要解调器配合。注意1 - 2*expected这个映射0 映 1、1 映 -1和前面 BPSK 调制里的0 - -1, 1 - 1是反的。这不是笔误两种约定都有人用但必须全链路统一。混用的话译码器会系统性地把所有比特判反误码率恒等于 0.5 且完全不随信噪比变化——这个现象后面还会细说。4.2 加比选每个时刻对每个状态只留一条幸存路径ACS 是 Viterbi 的心脏。每个时刻对每个状态把所有能到达它的分支都算一遍「前驱路径度量 分支度量」取最小的那条留下其余的丢弃。因为后续的路径只依赖当前状态和当前度量值被丢弃的路径不可能再翻身这就是最优性成立的前提。function dec viterbi_hard(rx, trellis, tbDepth) % 硬判决 Viterbi 译码 % rx: 1 x (n*L) 硬判决比特 % trellis: build_trellis 生成的结构体 % tbDepth: 回溯深度 n trellis.n; S trellis.numStates; L floor(length(rx) / n); INF 1e9; pm INF * ones(S, 1); % 路径度量初始只有零态可达 pm(1) 0; surv zeros(S, L); % surv(状态1, t) t-1 时刻的前驱状态 decIn zeros(S, L); % decIn(状态1, t) 到达该状态所走的输入比特 for t 1:L r rx((t-1)*n (1:n)); newPm INF * ones(S, 1); for ps 0:S-1 if pm(ps1) INF, continue; end % 不可达状态直接跳过 for u 0:1 ns trellis.nextState(ps1, u1); out squeeze(trellis.output(ps1, u1, :)); bm sum(out ~ r); % 汉明距离 cand pm(ps1) bm; if cand newPm(ns1) % 加比选比 newPm(ns1) cand; % 更新度量 surv(ns1, t) ps; % 记录幸存前驱 decIn(ns1, t) u; % 记录幸存输入 end end end pm newPm; end % 回溯从度量最小的终态出发沿幸存路径倒推 [~, st] min(pm); st st - 1; dec zeros(1, L); for t L:-1:1 dec(t) decIn(st1, t); st surv(st1, t); end end逐段看pm初始化为[0, INF, INF, INF]表示编码器从零态出发其他状态在 t0 时刻不可达外层循环遍历时间内层遍历前驱状态和输入cand newPm(ns1)就是「比」这一步只有更优的才覆盖surv和decIn两个矩阵把决策历史全部存下来这是回溯的前提。参数说明tbDepth在这个完整实现里没有直接用上因为这里存了全程的决策历史。真正需要tbDepth的是滑窗版本——为了省内存只保留最近tbDepth个时刻的决策每译完一段就回溯一段输出。工程实现几乎都用滑窗但教学和仿真用全量回溯更直观也方便对比「不同回溯深度」的影响把surv和decIn截断到tbDepth列即可。4.3 回溯为什么回溯深度不能省回溯深度是个纯工程参数数学上没有它。理论上最优路径要一直追到 t0 才能确定第一个比特但实际中路径在若干步之后就会「合流」——两个状态的前驱在高信噪比下大概率收敛到同一条路径。所以回溯tbDepth步得到的判决和全量回溯的结果几乎一样而内存和延迟从 O(L) 降到 O(tbDepth)。问题在于tbDepth取多少才算够。经验值是5N到7N但这个「经验」有前提信噪比不能太低、帧长不能太短、码率不能太高。低信噪比时路径合流慢5N 可能不够短帧时连 5N 都凑不齐只能退化成全量回溯。我一般会做一次扫描固定其他参数让tbDepth从 N 开始按步长递增看误码率什么时候不再明显下降。对 (2,1,7) 码在 4 dB 附近这个拐点通常在 25 到 35 之间。低于 20 就开始明显恶化低于 10 基本等于没译码。4.4 完整译码函数与一次跑通的自测写完译码器第一件事不是接信道而是做一次「无噪声回环」编码后的码字直接喂给译码器看能不能原样还原。这一步能排掉绝大部分低级错误。% 无噪声回环自测编码 - 译码必须完全一致 N 7; g [171 133]; trellis build_trellis(g, N); info randi([0 1], 1, 500); m N - 1; coded conv_encode([info, zeros(1,m)], g, N); dec viterbi_hard(coded, trellis, 5*N); dec dec(1:500); fprintf(回环误比特数%d\n, sum(dec ~ info)); assert(isequal(dec, info), 无噪声回环失败先查网格图和状态编号);回环通过只说明编码和译码用的是同一张网格图还不能说明网格图本身是对的。第二步要和工具箱对齐用vitdec译同一段码字比较输出。第三步才是接 AWGN 看误码率曲线是否落在合理区间——(2,1,7) 码在 4 dB 附近硬判决的误码率应该在 1e-3 量级软判决应该更低。如果差了数量级先怀疑Eb/N0换算再怀疑度量公式。提示调试 Viterbi 时把pm每个时刻的最小值和最大值打出来看一眼。正常情况两者之差不会随时间无限增长如果差值越来越大说明度量没有归一化长帧会溢出或者精度丢失。5. 参数扫描与踩坑排查四组对比实验和五条血泪记录5.1 四组对比实验怎么组织脚本论文里做了四组对比对应四个参数。脚本层面不用写四份把主仿真包成函数参数从外面传进去就行function ber run_one_case(cfg) % 单组参数下的误码率仿真 % cfg 字段N, g, rate, K, tb, ebno, mode(hard/soft), nIter trellis build_trellis(cfg.g, cfg.N); ber zeros(size(cfg.ebno)); for i 1:length(cfg.ebno) errCnt 0; bitCnt 0; for it 1:cfg.nIter info randi([0 1], 1, cfg.K); m cfg.N - 1; coded conv_encode([info, zeros(1,m)], cfg.g, cfg.N); tx bpsk_mod(coded); snr cfg.ebno(i) 10*log10(2*cfg.rate); rx awgn_channel(tx, snr); if strcmp(cfg.mode, hard) dec viterbi_hard(double(rx 0), trellis, cfg.tb); else dec viterbi_soft(rx, trellis, cfg.tb); end dec dec(1:cfg.K); errCnt errCnt sum(dec ~ info); bitCnt bitCnt cfg.K; end ber(i) errCnt / max(bitCnt, 1); end end四组实验分别改cfg的一个字段码率组改rate和对应的g比如 1/2、2/3、3/4 的凿孔码约束长度组改N和gN 从 3 到 9回溯深度组改tb从 5 到 60判决方式组改mode。其他字段保持不变这样曲线之间的差异才能归因到那一个参数上。画图时统一横轴是 Eb/N0dB纵轴是误码率的对数坐标曲线数量控制在四条以内否则图例盖住数据。每组实验单独出一张图不要挤在一张里。5.2 五条踩坑记录坑一误码率曲线在高信噪比处不再下降形成「地板」现象Eb/N0 从 6 dB 加到 12 dB误码率卡在 1e-3 附近不动曲线几乎是水平的。原因回溯深度太短。tb给了 8 而 N7回溯步数比约束长度还少路径还没合流就被强行截断判决每次截断都引入一次随机错误。这个错误率与信噪比无关所以形成了地板。解决tb至少取5N短帧场景改全量回溯。改完再扫一遍曲线会在高信噪比处继续往下走。坑二软判决反而比硬判决差现象同样信噪比下viterbi_soft的误码率比viterbi_hard还高理论上不该出现。原因BPSK 映射和软判决度量里的符号约定不一致。调制端用的是0 - -1, 1 - 1译码端度量里写的是1 - 2*expected即0 - 1两者反了。译码器在找最不相似的路径自然越译越错。解决全链路统一一个约定写在脚本开头的注释里。我习惯用0 - -1, 1 - 1那么软判决的期望符号就是2*expected - 1。改完立刻见效。坑三编码后长度对不上尾部丢比特现象length(coded)不等于n * (K m)或者译码输出截掉前 K 位之后还剩一截误码率虚高。原因收尾比特没处理。要么编码前没补零让寄存器清零要么译码后截断的位置算错了截了K位但实际信息位不在最前面。解决编码前显式补m个零译码后显式截前K位两步都写成独立语句不要藏在函数调用里。补零造成的码率损失在 K 大于 1000 时可以忽略但要在论文里说明。坑四误码率恒等于 0.5完全不随信噪比变化现象任何 Eb/N0 下误码率都是 0.5 左右曲线是一条水平线。原因译码器根本没有在正确的网格上搜索。常见触发点有三个——生成多项式八进制转二进制时位序反了build_trellis里de2bi没加left-msb编码器和译码器用的不是同一张网格图。解决先用无噪声回环自测再用poly2trellisconvencvitdec做三方对齐。位序问题在对齐时会立刻暴露因为工具箱的行为是确定的。坑五低信噪比下译码比不编码还差现象Eb/N0 在 0 dB 附近时编码系统的误码率比未编码的 BPSK 还高曲线交叉。原因这不是 bug是正常现象。卷积码的纠错能力依赖于「错误是稀疏的」低信噪比下错误密度超过码的纠错半径译码器反而会把正确的比特翻成错的。香农限以下的区域本来就该这样。解决不要试图「修」这个问题。论文里应该把交叉点标出来并解释清楚这恰恰是编码增益概念的落脚点——编码增益是在给定误码率下比较的不是所有信噪比下都占优。另外补一条容易混淆的awgn函数默认按符号功率加噪如果信号功率不是 1比如 16QAM 归一化没做噪声方差会算错表现为整条曲线平移。BPSK 下tx是 ±1功率为 1不会有这个问题但换调制方式时必须检查。6. 二次开发改造换码型、加凿孔与验证方法6.1 换一个码型从 (2,1,3) 到 (2,1,7)改码型只需要动两个变量N和g。约束长度从 3 提到 7状态数从 4 涨到 64译码复杂度大约翻 16 倍但误码率在 1e-4 处能改善将近 2 dB。标准七号码的生成多项式是[171 133]八进制换上去之后一定要重跑无噪声回环和工具箱对齐因为位序问题的暴露概率随长度增加而上升。如果想试 (3,1,N) 这种 1/3 码率把g改成三行build_trellis和conv_encode都不用改它们对 n 是通用的。但viterbi_hard里的bm sum(out ~ r)会自动适配因为out和r的长度都由trellis.n决定。6.2 加凿孔用删余换码率凿孔puncturing是工程上调整码率的标准手段按固定图案删掉一部分编码输出接收端在对应位置插入「擦除」标记译码时这些位置的度量置零即可。1/2 母码删掉 1/4 的输出就是 2/3 码删掉 1/3 就是 3/4 码。改造点有三处编码后按图案删位译码前按同一图案插回擦除标记分支度量里遇到擦除位时跳过该位的贡献。第三处最容易写错正确做法是把该位的度量记为 0不贡献任何距离而不是当成 0.5 或者随机值。6.3 验证方法别只看曲线好看改完之后怎么确认没改错我一般走三步。第一步无噪声回环必须零误码这一条挂了说明网格图或者补零逻辑有问题。第二步和工具箱的vitdec逐比特比对一段带噪数据输出必须完全一致——不一致说明度量公式或者状态编号有偏差。第三步和bercoding给出的理论值比对量级硬判决在 4 dB 附近应该是 1e-3 量级软判决应该更好一截差了数量级就回去查Eb/N0换算。第三步容易被跳过但它其实最有价值。理论和实现的偏差如果稳定存在通常不是随机 bug而是某个系统性约定不一致比如码率换算漏了、收尾比特没扣掉、噪声方差按符号功率算了但信号实际不是单位功率。这种偏差看曲线看不出来和理论值一比就现形。最后一条是我自己的教训早期做这套仿真我把「无噪声回环通过」当成了「编码器写对了」的证明结果网格图里状态编号是反的回环照样能过因为编码译码用的是同一张错图接上信道才发现曲线整个是废的。从那以后我每次改完编解码代码无噪声回环、工具箱对齐、理论量级比对这三步都要强制走一遍一步都不省。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

电磁波吸收材料市场:82%集中度下,先进材料2%渗透率如何撬动7.56%增长
电磁波吸收材料市场:82%集中度下,先进材料2%渗透率如何撬动7.56%增长

简介:这份文档面向电子、通信、军工及材料领域的研究人员与市场分析从业者,系统梳理全球电磁波吸收材料行业的市场格局与技术演进路径,帮助读者理解传统吸波材料与先进吸波材料之间的替代趋势及竞争态势。压缩包内为1个docx文件,约… · 2026/11/3 10:53:23

医疗AI战略计划拆解:价值链、证据链与工程接口
医疗AI战略计划拆解:价值链、证据链与工程接口

简介:这份PDF是美国卫生与公众服务部(HHS)于2025年1月发布的《医疗卫生人工智能战略计划》官方文本,面向医疗AI研究者、政策制定者、医疗产品研发人员及公共卫生管理者,系统回答AI如何在医疗研究、产品开发与公共卫生服… · 2026/11/3 10:53:23

HackRF One软件无线电:驱动、SDRSharp与GNURadio实践
HackRF One软件无线电:驱动、SDRSharp与GNURadio实践

简介:HackRF One 软件无线电平台用户手册面向刚接触 SDR 的爱好者、嵌入式开发者与无线电实验人员,帮助其从硬件组成、Windows/Linux 开发环境搭建到 GNURadio 使用与发射接收实例建立完整认识。资源包仅含 1 个 PDF 文件,大小约 2.49MB&… · 2026/11/3 10:53:23

Redis键过期机制原理与实战应用详解
Redis键过期机制原理与实战应用详解

1. Redis键过期机制的核心原理Redis作为内存数据库的标杆产品,其键过期机制的设计直接影响着内存使用效率和应用稳定性。当我们在Redis中设置过期时间时,实际上是在操作一个独立的过期字典(expires字典),这个字典与主字… · 2026/11/3 11:43:23

专业内容创作指南与处理不完整信息建议
专业内容创作指南与处理不完整信息建议

1. 理解输入内容的缺失根据您提供的输入信息,项目标题为"111111",其他字段均为空。这种情况通常意味着:可能是测试输入或占位符用户可能误操作提交了不完整信息系统传输过程中出现了数据丢失2. 专业内容创作的基本原则作为专业内容… · 2026/11/3 11:43:23

SQL索引优化实战:从原理到性能提升150倍的秘诀
SQL索引优化实战:从原理到性能提升150倍的秘诀

1. 为什么SQL索引如此重要? 我至今还记得第一次遇到生产环境SQL查询超时的场景——一个原本只需要0.1秒的简单查询,在数据量增长到百万级后突然需要15秒才能返回结果。当时紧急排查发现,问题就出在没有正确使用索引上。这个教训让我深刻认识到… · 2026/11/3 11:43:23

Git版本回退原理与实战指南
Git版本回退原理与实战指南

1. 为什么我们需要版本回退? 在团队协作开发中,代码库就像是一个不断生长的有机体。想象一下这样的场景:你花了三天时间开发一个新功能,提交了十几个commit后突然发现——这个功能方向完全错了!或者更糟,你… · 2026/11/3 11:43:23

UE5 JSON解析:TJsonReader类族与性能优化实践
UE5 JSON解析:TJsonReader类族与性能优化实践

1. UE5 JSON解析基础与TJsonReader类族概述在Unreal Engine 5的C开发中,处理JSON数据是日常开发中无法回避的任务。引擎内置的JSON解析系统提供了一套高效、类型安全的处理方案,其核心就是TJsonReader类及其派生类。这套解析器采用流式处理模式&#xff… · 2026/11/3 11:43:23

WinForms开发中的死锁问题与解决方案
WinForms开发中的死锁问题与解决方案

1. 死锁的本质与四大必要条件 在WinForms应用开发中,当我们在UI线程与后台任务之间频繁切换时,稍不注意就可能陷入死锁的泥潭。死锁就像两个人在狭窄走廊相遇,谁也不肯后退一步,结果谁都过不去。从技术角度看,死锁是指… · 2026/11/3 11:42:22

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战
STM32F411RE与MAX14431实现SPI隔离:工业现场抗共模干扰实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题
ESP32+大模型AI硬件开发:从Demo到产品的8个工程问题

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践
基于MAX14431BAEE+与PIC18F85J10的SPI隔离方案设计与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 1:01:59

双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/2 13:10:28

STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 6:04:24

DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/3 5:52:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码