MATLAB 仿真实现任意(n,k,m)卷积码译码

MATLAB 仿真实现任意(n,k,m)卷积码译码

  上一节已经给大家展示了如何在MATLAB中进行任意的(n,k,m)卷积码编码仿真操作,并给了具体函数。自取MATLAB (n,k,m)卷积码原理及编码仿真代码(你值得拥有)。
在一番挣扎后,终于现在搞懂了卷积码的译码原理并用成功用MATLAB实现仿真,话不多说,上号!

1 卷积码的维特比译码算法(Viterbi decoding algorithm)译码

  还是和上次一样,这种原理性的东西目前网上已经有太多了,我就不一一介绍了。我比较推荐大家找本有关于卷积码的书看,或者上B站看一些相关视频,等有一个大格局的概念(既有一个整体流程以后),再看相关博客对卷积码译码的介绍(毕竟一般博客的内容比较范,不详细)。

1.1 维特比译码算法

  纠正随机错误的卷积码分为两类:一类是用代数方法译码的码,另一类是用概率译码的码。大数逻辑译码门限译码的一种,属于代数译码。最早由梅西于1963年提出。1967年鲁滨逊等利用差集三角构造了一批可用大数逻辑译码的自正交码。大数逻辑译码是卷积码代数译码中最主要的译码方法。
  而Viterbi译码算法是一种最大似然译码(MLD)算法,是1967年由维特比(Viterbi)提出的一种概率算法。在码的约束度较小时,它具有效率高、速度快、译码器较简单等特点。因而自从Viterbi算法提出以来,无论在理论上还是实践上都得到了迅速发展,并广泛应用于各种数据通信系统中。

1.2 硬判决维特比译码算法

  Viterbi译码算法成功地解决了寻找最大路径度量时计算量随长度L指数增长这一问题。它并不是在网格图上一次比较所有可能的2kL条路径,而是采用迭代的方法,接受一段,计算、比较一段,选择一段最可能的码段,从而达到整个码序列是一个具有最大似然函数(或最小汉明距离)的序列。Viterbi译码算法可以分为一下几个步骤:
   (1) 从某一时间单元j开始,计算出进入每一状态的所有路径的路径度量值,然后进行比较,保存具有最大路径度量的路径及其路径度量值,而删除其他路径。被保存下来的路径被称为留存(或幸存)路径。
  (2) j加1,把此刻进入每一状态的所有分支度量和同这些分支相连的前一时刻的留存路径的度量相加,得到并保存此时刻进入每一状态的留存路径 及其路径度量值,删除其他路径。因此,留存路径延长了一个分支。
  (3) 若j   在Viterbi译码算法中,对于(n,k,m)卷积码,每一步迭代需计算、比较2(m+1)kL条,这就将计算量从L的指数函数将为L的线性函数,因而大大减少了计算量。
  具体流程此处就不提及,网上已有许多参考资料,推荐可以上B站看相关视频
  (1.1 1.2节选自由张辉、曹丽娜老师主编的第四版现在通信原理与技术

2 MATLAB仿真实现任意(n,1,m)卷积码译码

  和上一节一样,先仿真k=1的情况,再仿真任意k的情况,有助于理解与编程。分别以生成多项式为[5 7 7](3,1,2)卷积码生成多项式为[5 7](2,1,2)卷积码为例,证明此函数的正确性。上代码!!!

2.1 实验一

%% 生成多项式[5 7 7] (3,1,2) 利用MATLAB函数编码解码
M = [1 1 0 0 1 0 1 1];
trellis = poly2trellis(3,[5 7 7]);
convo = convenc(M,trellis);
decode_num = vitdec(convo,trellis,7,'trunc','hard');
%% 生成多项式[5 7 7] (3,1,2) 自己写的编码与解码函数
M = [1 1 0 0 1 0 1 1];
G = [1,0,1;1,1,1;1 1 1];
C = ZW_conv_encode(M,G);
CC=ZW_conv_decode(C,G);

%% 验证自己编写的编码与译码函数的正确性
res1 = C - convo %全为零则编码正确
res2 = CC - decode_num %全为零则译码正确

2.2 实验二

%% 生成多项式[5 7] (2,1,2) 利用MATLAB函数编码解码
M = [1 1 0 0 1 0 1 1];
trellis = poly2trellis(3,[5 7]);
convo = convenc(M,trellis);
decode_num = vitdec(convo,trellis,7,'trunc','hard');
%% 生成多项式[5 7] (2,1,2) 自己写的编码与解码函数
M = [1 1 0 0 1 0 1 1];
G = [1,0,1;1,1,1];
C = ZW_conv_encode(M,G);
CC=ZW_conv_decode(C,G);
res1 = C - convo %全为零则编码正确
res2 = CC - decode_num %全为零则译码正确

2.3 实验结果

  最终res1及res2均为零向量,所以任意(n,k,m)卷积码编码及译码函数的编写应该是正确的。

3 MATLAB仿真实现任意(n,k,m)卷积码译码实验

  上大号了兄弟们,开始验证任意(n,k,m)卷积码译码函数的正确性!也是以两个实验为例,分别为生成多项式为[2 1 3;1 3 3](3,2,[1 1])卷积码生成多项式为[7 1 4;2 5 7](3,2,[3 3])卷积码。此外,为了加大难度,找了一个生成多项式为[23 35 0;0 05 13](3,2,[5 4])卷积码进一步验证我编写函数的正确性。上号!

3.1 实验一

%% 生成多项式[7 1 4;2 5 7] (3,2,[3 3]) MATLAB内部函数
M = [1 1 0 0 1 0 1 1];
trellis = poly2trellis([3 3],[7 1 4;2 5 7]);
convo = convenc(M,trellis);
%decode_num = vitdec(convo,trellis,32,'trunc','hard');
%% 生成多项式[7 1 4;2 5 7] (3,2,[3 3]) 自己编写函数
M = [1 1 0 0 1 0 1 1];
G1{1} = [1 1 1;0 0 1;1 0 0];
G1{2} = [0 1 0;1 0 1;1 1 1];
C2 = ZW_conv_K_encode(M,G1);
C_K_decode = ZW_conv_K_decode(C2,G1);
res1 = C2 - convo %全为零则编码正确
res2 = C_K_decode  - M %全为零则解码正确

3.2 实验二

%% 生成多项式[2 1 3;1 3 3] (3,2,[1 1]) MATLAB自带函数
M = [1 1 0 0 1 0 1 1];
trellis = poly2trellis([2 2],[2 1 3;1 3 3]);
convo = convenc(M,trellis);
%decode_num = vitdec(convo,trellis,32,'trunc','hard');
%% 生成多项式[2 1 3;1 3 3] (3,2,[1 1]) 自己编写函数
M = [1 1 0 0 1 0 1 1];
G1{1} = [1 0;0 1;1 1];
G1{2} = [0 1;1 1;1 1];
C2 = ZW_conv_K_encode(M,G1);
C_K_decode = ZW_conv_K_decode(C2,G1);
res1 = C2 - convo      %全为零则编码正确
res2 = C_K_decode  - M %全为零则解码正确

3.3实验三

%% 生成多项式[23 35 0;0 05 13] (3,2,[5 4])
M = [1 1 0 0 1 0 1 1];
trellis=poly2trellis([5,4],[23,35,0;0,05,13]);
convo = convenc(M,trellis);
G1{1} = [1 0 0 1 1;1 1 1 0 1;0 0 0 0 0];
G1{2} = [0 0 0 0;0 1 0 1;1 0 1 1;];
C2 = ZW_conv_K_encode(M,G1);
C_K_decode = ZW_conv_K_decode(convo,G1);
res1 = C_K_decode - M; %全为零则编码正确
res2 = convo - C2;	   %全为零则解码正确

3.4实验结果

  以上实验结果中res1和res2的结果均为全零向量,所以可以证明自己编写的函数正确性。
  之所以突然加上第三个实验,是因为我突然意识到前面两个实验(n,k,m)卷积码里面m中元素都相同,为了实验的严谨性,选了不同的元素,即m=[5 4]。进一步验证此函数的通用性及正确性。

4.MATLAB仿真实现任意(n,k,m)卷积码译码函数

  既然函数没有问题的话,那现在就到了最激动的开源环节了!!!话不多说,上代码!

4.1 (n,1,m)的卷积码译码函数

首先介绍一下(n,1,m)卷积码的译码函数

%% 本函数实现卷积码译码
function C = ZW_conv_decode(m,G)
%{
输入:
    接收到的卷积码 m
    生成矢量G  每一行代表一个输出的连接结构 与发送机编码器结构相同
输出:
    卷积码译码结果C 
特点:
    适用于所有的[n,1,N]卷积译码
%}
len = length(m);    %记录输入信息长度
K=1;                %每次输入一个码元
%通过 n和len的值来判断总共需要几个解码循环
[n,N] =  size(G);   %n表示发送端一次有几个码元输出 N表示一次有几个监督码元(包括当前输入)
times = len/n;      %解码循环总次数 即原信息的长度
%states = ZW_B_Comb(N-1);    %共有N-1种状态 states存储了每一种状态

%状态寄存器 由于有N-1个编码器(不包含输入) 所以共有种状态 最多时候每种状态有两种路径 所以共有2^N种情况
%每种状态又有累计路径值、累计输入、以及当前状态三种情况 
C_regs = cell(2^(N),4);  %每一列代表一个状态 同一列中每一行分别表示当前状态的不同情况
C_regs_reg = cell(2^(N-1),3);
%初始时从网格图中第一个状态开始出发 为0 
C_regs{1,1} = 0;       %第一行代表此时这个状态累计路径值 初始为零
C_regs{1,2} = [];      %第二行存储输入的信息 一开始没有输入
C_regs{1,3} = zeros(1,N-1);   %第三行存储该寄存器此时存储的状态是什么 共2^(N-1)可能
C_regs{1,4} = 1;       %第四行代表是否被征用 1代表这个寄存器被征用了 0代表空闲
for i=2:1:2^(N)     %将初始累计值均设置为0 且初始时除1外全为空闲
    C_regs{i,1}=0;
    C_regs{i,4}=0;
end
for i=1:times
    code_buffer=m(1,(i-1)*n+1:i*n); %第一次输出时对应的码元 共n个
    a_b=find([C_regs{:,4}]==0); %返回为0的序号 看哪个没有被使用
    n_b=2^N-length(a_b);          %说明有n_b个征用的状态等待去进入下两个状态
    %第i个元素储存了编码器对应状态的前两条输入对应的寄存器序号  此状态二进制换成十进制正好等于i-1 
    states = cell(1,2^(N-1));  
    for j=1:n_b
        in_0 = j;       %第j个与此时是序号最靠前的第一个为空闲的状态C_regs的 将是上一个第j个输入分别为01后
        in_1 = a_b(j);  %对应的状态
        put_0 = mod( [0 C_regs{j,3}] *G.',2); %求出输入为0时,此状态对应的输出
        put_1 = mod( [1 C_regs{j,3}] *G.',2); %求出输入为1时,此状态对应的输出
        
        C_regs1_in_0 = C_regs{j,1} + sum(xor(put_0,code_buffer)); %计算输入为0时的路径累计值(汉明距离)
        C_regs1_in_1 = C_regs{j,1} + sum(xor(put_1,code_buffer)); %计算输入为0时的路径累计值
        C_regs2_in_0 = [C_regs{j,2} 0];    %得到这条路径的累积输入信息码
        C_regs2_in_1 = [C_regs{j,2} 1];    %得到这条路径的累积输入信息码
        C_regs_in_0 = [0 C_regs{j,3}];    %计算输入为0时的下一个状态的情况 
        C_regs_in_0(end) = [];               %删除多余单元
        C_regs{in_0,4} = 1;                  %代表被征用 
        states{1,ZW_B_T(C_regs_in_0)+1} = [states{1,ZW_B_T(C_regs_in_0)+1} in_0];%储存相同状态的两条路径
       
        C_regs_in_1 = [1 C_regs{j,3}];    %计算输入为1时的下一个状态的情况
        C_regs_in_1(end) = [];               %删除多余单元
        C_regs{in_1,4} = 1;                  %代表被征用  
        states{1,ZW_B_T(C_regs_in_1)+1} = [states{1,ZW_B_T(C_regs_in_1)+1} in_1];%储存相同状态的两条路径
        % 计算完毕后再一并赋值 不能就算一个就赋值一个 因为in_1也要用到in_0原来的值
        C_regs{in_0,1} = C_regs1_in_0;
        C_regs{in_1,1} = C_regs1_in_1;
        C_regs{in_0,2} = C_regs2_in_0;
        C_regs{in_1,2} = C_regs2_in_1;
        C_regs{in_0,3} = C_regs_in_0;
        C_regs{in_1,3} = C_regs_in_1;
    end
    if n_b == 2^(N-1)   %说明现在每个状态都有两个输入 该将每个状态的最小累计路径取出来并删除最大累计路径了
        for k=1:2^(N-1)
            %Len = length(states{1,k});    %计算每个状态对应来源路径的累计路径数
            if C_regs{states{1,k}(1),1} > C_regs{states{1,k}(2),1} %选出最近的路径所在的寄存器序号
                i_tmw = states{1,k}(2);
            else
                i_tmw = states{1,k}(1);
            end
            %weight_list=[C_regs{states{1,k},1}];   %找出当前状态对应来源路径的累计路径值
            %[~,i_tmw]=sort(weight_list); %从小到大排列 并返回此时序列对应排列前的序号
            C_regs_reg{k,1}=C_regs{i_tmw,1};%只找出每个状态最短的累计路径 
            C_regs_reg{k,2}=C_regs{i_tmw,2};%并重新存储在序号前2^(N-1)的状态寄存器中
            C_regs_reg{k,3}=C_regs{i_tmw,3};
        end
        for k=1:2^(N-1)    %将挑选出来的2^(N-1)个状态重新从小到大赋给原寄存器
            C_regs{k,1}=C_regs_reg{k,1};%只找出每个状态最短的累计路径 
            C_regs{k,2}=C_regs_reg{k,2};%并重新存储在序号前2^(N-1)的状态寄存器中
            C_regs{k,3}=C_regs_reg{k,3};
        end
        for L=2^(N-1)+1:2^N %将后2^(N-1)个寄存器释放
            C_regs{L,4}=0;  
        end
    end
end
n_survival_branch=length(find([C_regs{:,4}]==1)); %找出被征用(存储了状态的寄存器序号) 一般为前2^(N-1)
w_survival_branch=[C_regs{1:n_survival_branch,1}];%找出被征用(存储了状态的寄存器序号)的累计路径值 
[~,i_weight]=sort(w_survival_branch);             %把各状态的累计路径值从小到大排列 并返回对应从前所在序列的序号值
i_minimum_weight=i_weight(1);                     %i_weight(1)最后所有状态中累计路径值最小的状态序号
C=C_regs{i_minimum_weight,2};                     %输出最有可能的输入情况

  此代码参考自这位大佬,引路【信道编码/Channel Coding】卷积码和Viterbi译码及其MATLAB实现。不同的是,此博客代码最后每次选出的是最短的两条路径,可能对应的一个状态。而我是将每个状态的路径都最后都保留一条,并不会提前把某一个存在的状态提前抹去。读者可自行通过代码体会其不同处(并无一捧一踩之意,请不要误会) 。

4.2 (n,k,m)的卷积码译码函数

%% 本函数实现卷积码译码
function C = ZW_conv_K_decode(m,G)
%{
输入:
    接收到的卷积码 m
    生成矢量G  元组 与发送机编码器结构相同
        每一个细胞对应一个输入的生成式 输入(1,k)  每一行代表一个输出的连接结构 
输出:
    卷积码译码结果C 
特点:
    适用于所有的[n,k,N]卷积译码
%}
len = length(m);    %记录输入信息长度
[~,k] = size(G);    %k表示每次编码器中输入的信息码元数

%预先分配内存 加快运行速度
N = zeros(1,k);     %不过每个码元对应的输出码元数肯定相同 所以n均相同
states = ZW_B_Comb(k);%得到k个输入编码时对应所有的输入情况 cell(1,2^k)每一个元组又是一个(1,k)
states_num = zeros(1,k); %第i个元素为截止到第i个输入码元时编码器总个数(包括前i个的以及自己的,不包括后面的)
for i=1:k           %求每次输入码元对应的编码器结构
    [n,N(i)] = size(G{i}); %n表示一次有几个码元输出 N表示一次有几个监督码元(包括当前输入)
    if i==1
        states_num(i) =(N(i) - 1);
    else
        states_num(i) = states_num(i-1) + (N(i) - 1) ; %当前一个输入码元路径对应的编码器结构数目加上上一个的
    end
end
%N
%states_num(k)
%总的状态数 每种状态又有累计路径值、累计输入、以及当前状态三种情况
%最大可能由有2^states_num个状态 每种状态又有2^k种输入(2^k条输出路径) 所有寄存器最大可能有2^states_num*2^k条路径
%第i个输入码元对应的编码器情况为2^(N(i)-1)种
C_regs = cell((2^states_num(k))*(2^k),4); %同一列中每一行分别表示当前状态的不同情况 
times = len/n;      %计算循环的次数 times*k为输入信息总长度

%每个码元对应的状态下一个可能值states_num(i)前个寄存器值此值对应相同 因为都是对应同一时刻输出
%每个状态只能存在以k为整数倍+1的序号的寄存器中 如第一个状态在1种 后面输出的状态放在1-k种 便于管理及理解
%所以C_regs中序号为(i-1)*k+1 - i*k的上个状态均相同  然后再通过比较所有相同状态的累计路径值 选出最小的路径
%从而得到最新有效的状态路径值 且路径条数再次变为2^states_num(k)种(即每一种状态对应一条路径)
%然后将这2^states_num(k)依次放在寄存器对应序号为(i-1)*2^k+1 
%即放在每个k大格子中的第一个寄存器中 每个大格子对应2^states_num(k)个寄存器 
%且每个大格子中的寄存器的上一级累积路径相同 毕竟都是从一个状态来的
%即每k个寄存器种只有第一个是有效的 后面的都是等待装在此状态对应的输出状态
%寄存器分配且理解好了 那下面的事会好办一些了
for i=1:1   %将第一次输入时的所有输入的码元对应的编码器寄存器初始化 只存在一个状态 所以就初始化第一个寄存器
    C_regs{i,1} = 0;    %累计路径值为0 每个前N(i)个寄存器值此值对应相同 因为都是对应同一时刻输出
    C_regs{i,2} = [];   %当前无输入情况下 累计输入无
    C_regs{i,3} = [];%全部编码器初始状态为零 主要用于多路径输入时判断是哪些路径序号
    C_regs{i,4} = 1;    %设置为被占用状态
end

for i=2:(2^states_num(k))*(2^k) %将初始累计值均设置为0 且初始时除1外全为空闲
    C_regs{i,1}=0;
    C_regs{i,4}=0;
end
 %暂时存储每一条路径对应的状态 由于有(2^states_num(k))*(2^k)条路径 
 %但是每次大循环中只会顶多需要有2^states_num(k)种路径需要计算
 %每条路径又有k条编码器 即每行代表一条路径 k列代表这条路径不同编码状态
C_regs_3 = cell(2^states_num(k),k);  %每个细胞里面的值需要不包括输入 即每个{i,k}(1,N(i)-1)(不包括输入)
for i=1:k       %将初始状态的k个输入对应的编码器均初始化为0
    C_regs_3{1,i}=zeros(1,N(i)-1);
end

C_regs_1 = zeros(1,(2^states_num(k))*(2^k)); %暂时存储每一条路径对应权重
C_regs_2 = cell(1,(2^states_num(k))*(2^k)); %暂时存储每一条路径输入
%C_regs_33 = cell(1,(2^states_num(k))*(2^k));%暂时存储每一条路径编码器总状态 

C_regss = [];
for i=1:2^states_num(k)
    C_regss = [C_regss (i-1)*2^k+1];%把每个可能有效寄存器的序号找出来(找每个大格子的大哥所在地址)
end
for i=1:times   %共大循环这么多次 对应其输入次数
    %put_L = cell(1,2^k)%暂时储存每次k个输入的输出情况
    code_buffer = m(1,(i-1)*n+1:i*n);
    a_b=find([C_regs{C_regss,4}]==1); %返回为1的序号 看哪个大格子被使用
    n_b=length(a_b);             %记录下需要讨论输入的状态数 进入循环开始讨论
    %用于存储拥有相同状态的寄存器序号 共2^states_num(k)种状态
    index_C = cell(1,2^states_num(k));%每次大循环时重新更新
    %暂时存储 每一条路径 的k条编码的更新状态 由此判断哪些是寄存器是属于同一状态 每次大循环更新
    CC_regs_3 = cell((2^states_num(k)*(2^k)),k); %包括新输入 除去最后一个编码器的旧输入
    C_regs_33 = cell(1,(2^states_num(k))*(2^k)); %每次更新 用于将k个编码器组合起来
    for jj=1:n_b      %循环有用的状态
        for j=1:2^k   %每次遍历所有码元输入的情况 states{j} 求出每一条路径对应的输出 共2^k条
            put_L = cell(1,2^k);%暂时储存每次k个输入的输出情况 每条路径循环后重新更新
            states_input = states{j};%讨论第j种输入情况
            for L=1:k %在当前码元输入情况确定的情况下 讨论每条路径下每个输入码元对应输出编码器的状态
                %[states_input(L) C_regs_3{jj,L}] %当前码元对应编码器的状态(包括输入)
                %put_L{L}求出输入为states_input时,此状态对应的输出 put_L{L}(1,n)
                %mod([states_input(L) C_regs_3{jj,L}]*G{L}.',2)为当前输入码元对应输出
                %加上put_L{L} 即加上这个大循环中上个码元对应的输入情况 再对2取余 即得到前几个码元输出共同结果
                if L~=1
                    put_L{j} = mod(mod([states_input(L) C_regs_3{jj,L}]*G{L}.',2)+put_L{j},2);
                else
                    put_L{j} = mod([states_input(L) C_regs_3{jj,L}]*G{L}.',2);
                end
                %同时更新第jj个大格子的状态所在的第j条路径的第L个编码器所处状态
                CC_regs_3{(jj-1)*(2^k)+j,L} = [states_input(L) C_regs_3{jj,L}];
                CC_regs_3{(jj-1)*(2^k)+j,L}(end) = []; %得到次路径第L个编码器的状态(不含输入码元)
                CC_regs_3{(jj-1)*(2^k)+j,L};
            end
            %第jj个大格子的状态所在的第j条路径累加值
            C_regs_1(1,(jj-1)*(2^k)+j) = C_regs{(jj-1)*(2^k)+1,1} + sum(xor(put_L{j},code_buffer));
            %第jj个大格子的状态所在的第j条路径总输入信息
            C_regs_2{1,(jj-1)*(2^k)+j} = [C_regs{(jj-1)*(2^k)+1,2} states_input];
        end
        %当一个状态的所有输出情况遍历完毕后 上面程序自然就把一个状态的所有输出按顺序放在(jj-1)*2^k+1 - jj*2^k中了
        for M=(jj-1)*2^k+1:jj*2^k
 %           C_regs{M,3} = [];
 %           C_regs{M,1} = C_regs_1(1,M);
 %           C_regs{M,2} = C_regs_2{1,M};
            for P=1:k
 %               C_regs_3{M,P} = CC_regs_3{M,P}; %存储每一条路径对应k个编码器状态
 %               C_regs{M,3} = [CC_regs_3{M,P} C_regs{M,3}]; %将k个编码器状态组合起来
                C_regs_33{1,M}= [CC_regs_3{M,P} C_regs_33{1,M}];%将k个编码器状态组合起来
            end
            %将是这个状态的寄存器序号存储在index_C中 每个状态都固定了index_C的位置 
            %即index_C相同序号中的元素都是拥有同一个状态的寄存器序号 且此序号最终从小到大排列
            index_C{1,ZW_B_T(C_regs_33{1,M})+1} = [index_C{1,ZW_B_T(C_regs_33{1,M})+1} M]; 
        end
    end
    %有用状态的下一个输出全部判断完毕 接下来准备将这些有多条输入路径的输出状态操作 把他们输入路径只剩一条
%    if length(index_C{1,Q}) < 2 %说明还没有两条输入或者压根还没有这个状态出现
    if n_b*(2^k) <= (2^states_num(k)) %总路径数 说明还没有超标
        for Z=1:n_b*2^k  %目前已有状态数 所有状态都是有效状态 依次附在大格子上的第一个小格子上
            C_regs{(Z-1)*2^k+1,1} = C_regs_1(1,Z);
            C_regs{(Z-1)*2^k+1,2} = C_regs_2{1,Z};
            C_regs{(Z-1)*2^k+1,3} = C_regs_33{1,Z};
            C_regs{(Z-1)*2^k+1,4} = 1;
            for W=1:k
                C_regs_3{Z,W}=CC_regs_3{Z,W};  %为下一次的有效状态的循环做准备
            end
        end
        for Z=n_b*2^k+1:length(C_regss) %释放没有被占用的
            C_regs{C_regss(Z),1} = 0;
            C_regs{C_regss(Z),4} = 0;
        end
    else 
        for Q=1:2^states_num(k) %遍历每一个状态所在的寄存器序号
            %C_regs_1(1,index_C{1,Q}) 取出当前寄存器序号对应的累计路径值
            [~,min_index]=min(C_regs_1(1,index_C{1,Q}));%返回最小值对应的序号
            index_put = index_C{1,Q}(min_index);        %对应需要保留的路径序号
            C_regs{(Q-1)*2^k+1,1} = C_regs_1(1,index_put);%需要保存的依次保留在大格子中的第一个小格子
            C_regs{(Q-1)*2^k+1,2} = C_regs_2{1,index_put};
            C_regs{(Q-1)*2^k+1,3} = C_regs_33{1,index_put};
            C_regs{(Q-1)*2^k+1,4} = 1;
            for W=1:k
                C_regs_3{Q,W}=CC_regs_3{index_put,W};
            end
        end
    end
end
branchs = [C_regs{C_regss,1}]; %最终可能存在的路径累计路径值
n_survival_branch=length(find([C_regs{C_regss,4}]==1)); %找出被征用(存储了状态的寄存器序号) 一般为前2^(N-1)
w_survival_branch=branchs(1:n_survival_branch);%找出被征用(存储了状态的寄存器序号)的累计路径值 
[~,i_weight]=sort(w_survival_branch);             %把各状态的累计路径值从小到大排列 并返回对应从前所在序列的序号值
i_minimum_weight=i_weight(1);                     %i_weight(1)最后所有状态中累计路径值最小的状态序号
C=C_regs{(i_minimum_weight-1)*(2^k)+1,2};                     %输出最有可能的输入情况

  此份代码比较长,变量过多,因为包含很多暂存值需要保存,比如输入后的各个编码器的状态,不能直接赋值给下一个输入的编码器的状态,因为此时的编码器状态还需要在这个输入中的其他部分后续会用到,如果改动了这个变量的值,则后续计算也会受到影响,故先保存,不改变,等后面计算不需要这个时刻的输入编码器状态时再赋值。
  (为方便理解,后面我会考虑找时间在纸上或者用相应软件粗略的把其大概流程画出来)

4.3 译码函数内其它自编函数

%% 本函数实现输出n位二进制所有组合情况
%兄弟们 我将使用数电里时序电路里的计数器来完成这个功能!
function C = ZW_B_Comb(n)
Q = zeros(1,n);         %从左至右分别对应Q0至Qn-1 初始状态均为零
C = cell(1,2^n);        %存储所有输出组合 共2^n次方种
for i=1:2^n             %每次进入循环 类似来了一个时钟
    C{i} = Q;
    for j=n:-1:2  %判断每个Q的状态 由于是顺序执行 由Q的状态方程知 得从Qn的表达式开始执行
        Q_j = ZW_Q_And(Q,j);    %寄存Q1-Qn-1相与的值
        Q(j)= ( Q_j & (~Q(j)) ) | ( (~Q_j) & Q(j) );
    end
    Q(1) = xor(Q(1),1); %最低为每来一个时钟取一次反 0-1或者1-0
end
%查看结果
%for i=1:2^n
%    C{i}
%end

   一直在想该怎么讨论输入码元的每一种情况,加入k各输入码元,那应该有2k种输入情况,那怎么一一罗列呢,这个问题也困绕了好久,终于,由于最近接触到了一些数电知识,利用加法器计数器原理,处理好了这个问题!果然多学习扩展自己的知识面是很重要的!

%% 本递归函数实现输出Q1-Qn-1的值
function C = ZW_Q_And(Q,n)  % n>1
if n==2
    C = Q(1);
else
    for i=1:n-2
        C = Q(i) & Q(i+1);
    end
end
%% 本递归函数实现由二进制得出十进制
function C = ZW_B_T(Q)  
    len = length(Q);
    C=0;
    for i=1:len
        C = Q(i) * 2^(i-1)+C;
    end
end

5 总结

   这份任意(n,k,m)卷积码译码的代码目前的局限性就是:它只能实现硬判决,而实践时软判决更好,以后有时间我再试试软判决。不过目前下一步工作就是实践了——利用FPGA实现卷积码的编码与译码功能。这个实践操作可要比仿真难多了,(学长早已经实现了这步,我就没继续做了) 这次仿真就已经把我给掏空了,真的来之不易啊!!!特别是编写这份任意(n,k,m)卷积码译码函数时,BUG频出,我是把代码看了一遍又一遍,改了一处又一处,甚至无奈时只能使用最残酷的手段:看每一步的代码输出情况,找出具体位置的错误。不得不说啊,虽然很累,但是还是挺有效的,终于是成功了!!好了,趁着这段空闲时间整好了仿真部分,等后面再有空了就去实践FPGA环节了,希望不会太久!

鸣谢博客

  1. 【信道编码/Channel Coding】卷积码和Viterbi译码及其MATLAB实现
  2. poly2trellis(matlab)
  3. 实验七-卷积编码的MATLAB实现
       这三份博客帮了我很大的忙,再次感谢!

你可能感兴趣的:(卷积码编码与译码,matlab,算法,开发语言)