可用于SDR的C语言纠错编码通用算法收集(3)-卷积码与Viterbi译码
为了配合学生借助 taskbus进行纯控制台STDIO模块的开发,我们收集了手头的通用纠错算法,便于学生进行开发学习。今天介绍卷积码以及维特比译码算法。在我自己以前学习《通信原理》的时候,总是对维特比算法的实质有些把握不清楚,实现起来,颇煞费周章。
1. 卷积纠错
对一个无限长的二进制序列,每个时钟时间轴向前流淌1比特。按照特定的位置关系,将当前时钟数据与过去若干时钟的数据模二加,得到校验结果,相当于把一个无限长多项式与校验多项式进行卷积运算。
1.1 序列角度
比如一个最简单的2,1,3码,其生成式八进制可以是13,17, 第一路13相当于二进制 001 011,也就是 1+y2+y31+y^2+y^31+y2+y3。这个多项式与无限长序列卷积,可看做下表:
时钟 | x−3x_{-3}x−3 | x−2x_{-2}x−2 | x−1x_{-1}x−1 | x0x_0x0 | x1x_1x1 | x2x_2x2 | x3x_3x3 | x4x_4x4 | x5x_5x5 | … |
---|---|---|---|---|---|---|---|---|---|---|
0 | 111 | 111 | 0 | 1 | ||||||
1 | 111 | 111 | 0 | 1 | ||||||
2 | 111 | 111 | 0 | 1 | ||||||
3 | 111 | 111 | 0 | 1 | ||||||
4 | 111 | 111 | 0 | 1 | ||||||
结果 | x−3+x−2+x0x_{-3}+x_{-2}+x_0x−3+x−2+x0 | x−2+x−1+x1x_{-2}+x_{-1}+x_1x−2+x−1+x1 | x−1+x0+x2x_{-1}+x_{0}+x_2x−1+x0+x2 | x3+x1+x0x_3+x_1+x_0x3+x1+x0 | x4+x2+x1x_4+x_2+x_1x4+x2+x1 |
从时钟0开始,每个时钟输出的结果是当前时钟与之前2、3个时钟的模二加。第二路1,7,也就是二进制 001 111,原理是一样的。经过这样处理后,输入一路数据,输出2路校验,形成了典型的1/2速率卷积码。
1.2 电路图角度
这种结构也可以用电路图表示:
一个具备3个缓存器的电路,每个时钟刷新一次。输出为当前时钟数据与缓存器中的若干状态的模二加。下个时钟,缓存器向右移存,新的数据推入,周而复始。
1.3 状态机角度
如果把缓存器的状态看做一个整数,则每次输入作为激励,会让这个状态不停地跳转。比如上述最简单的2,1,3码,其生成式八进制可以是13,17, 对应状态图
寄存器有3个,状态共2^3 = 8个,由于输入不同,跳转也不同。比如,状态跳转为s0->s4->s2->s5->s6->s7->s3->s1->s0…, 会产生输出2,1,0,1,1,1,0,2…
2 维特比译码
维特比算法会找出一条最优的路径,按照这条路径跳转产生的输出,与接收到的实际输出之间空间距离(硬判决用汉明距离,即比特重量)最小。接着上面的例子,因为传输错误,接收到的输出为2,1,0,1,[0],1,0,2…,第五个输出错了。通过译码,可以还原出原始路径,理由是按照这条路径,产生的全路径误差最小。
状态机是维特比的核心,具体输入什么,跳到哪去,完全取决于相应的编码。上述前向(无反馈)卷积码可以产生状态机,有反馈的feedback码,同样产生状态机。理论上,哪怕有人新发明一种纠错码,只要可用LSM来描述,就可以使用维特比译码。当然,要保证用好生成式确保好的代数性质,使得正确路径与其他路径产生的输出尽可能不同。
维特比算法并不是只针对卷积码的,可以说,它是一个普适的有限状态机算法(LSM)。只要有一个LSM,能够受不同输入的触发,在各个状态间跳转,并产生输出,就满足可基本条件,可根据状态机利用Viterbi 来从污染的输出中还原输入。
2.1 基本原理
建立一个动态的跳转结构,记录并更新每个时钟下,抵达各个状态的最优路径。
上图显示了前后两个时钟的状态。
- 根据状态机,当假设当前时钟输入各为0,1时,可得到下一时钟的状态。同时,跳转过程导致新的输出,追加到当前时钟的输出队列尾部(队列过长后,首部的数据就弹出丢弃)。
- 各个状态都进行跳转,得到新的状态。每一新的状态,都对应了2个队列,分别来自不同的跳转路径。
- 将各个状态的队列,与实际接收到的结果比对,只保留最优的1组。
- 时钟推进,周而复始。
- 每次当队列首部最老的数据弹出前,看看总误差最小的队列是谁,把对应位置的输入作为译码的结果。
2.2 数据结构
译码器采用结构体描述上述算法
//存储从当前状态到下一状态的跳转
typedef struct {int * nNextStatus; //下一状态int * nOutput; //跳转输出
} tag_statusjmp_next;//存储当前状态来自哪个旧状态的跳转
typedef struct {int * nLastInput; //上一次的触发int * nLastStatus; //上一状态int * nLastOutput; //跳转输出
} tag_statusjmp_last;//最优路径表,采用double_buffer技术,避免频繁动态内存分配
typedef struct {unsigned long long nCurrentHamming[2]; //当前全路径汉明int nInitialStatus[2]; //本路径的起始初态unsigned long long *nHamming[2]; //各个跳转的汉明int *nInChain[2]; //各个跳转的输入(触发)
} tag_path;/** \brief 有限状态机编译码器* 有限状态机有若干状态,因用户输入的触发,在状态间跳转。跳转的过程中* 跳转过程中,产生了跳转输出。受到信道污染的输出被重新送入译码器,* 通过维特比算法,译码器会纠正一定量的错误。*/
typedef struct
{int reg_status; //寄存器状态数,例子2,1,3为8int data_status; //输入状态数,例子为2, 只有两个状态0,1int code_bitlen; //输出比特数,例子为2, 双路输出int L;//队列记忆长度,如64tag_statusjmp_next * table_nextjmp/*reg_status 组 */;tag_statusjmp_last * table_lastjmp/*reg_status 组*/;tag_path * best_path/*reg_status 组*/;int nClock; //当前时钟int * vec_decoded_data; //存储译码结果的缓存int vec_decoded_data_msz;int vec_decoded_data_sz;long long nBestHamming; //译码器当前的最优汉明int * nBestStatus; //所有最优路径的索引int nBestStatus_sz;
} clsm_codec_arg;
2.3 环状双缓存技术
提高性能的关键技术是避免在跳转过程中的动态内存分配、内存搬移。
- 采用环状的缓存器,结合时钟取模,可以模拟有限有效长度下的无限序列。
- 采用奇偶两组镜像缓存,结合时钟取模,读A写B,读B写A,效率高高。
3. 调用方式
实际的例子如下:
int demo_216()
{int pins[] = {133,171};void * codec = new_conv_codec(2,1,6,pins,0,64);printf ("---------------\n216 hard viterbi dec\n");int inital_status = 0;int data[1024],code[1024],recv[1024];//100Kb data, 200KbCodefor (int i=0;i<100;++i){for (int j=0;j<1024;++j)data[j] = i*1717%2;inital_status = encode(codec,data,1024,code,inital_status);for (int i=0;i<5;++i)code[rand()%1024] ^= rand() % 4;decode(codec,code,1024,false);int e = curr_best_hamming(codec);int poped = pop_data(codec,recv,1024);printf ("group %03d Ham=%7d, poped %d symbols\r",i+1,e,poped);}delete_lsm_codec(codec);printf ("\n");return 0;
}
4. C重封装
主要接口如下:
//==============================卷积纠错码编译码================================
/*** @brief oc_conv_new_codec 分配一个卷积码编译码器* @param n 校验每符号n比特* @param k 信息每符号k比特* @param m 寄存器 m 时钟* @param poctPins 八进制前向电路管脚数组,如 133,171* @param pfbPins 八进制前向电路管脚数组,如 103,157* @param L 维特比记忆长度* @return 译码器指针*/
void * oc_conv_new_codec(int n, int k, int m,const int * poctPins/*k*n*/,const int * pfbPins/*k*k*/,int L);/*** @brief oc_conv_delete_codec 释放卷积码编译码器 codec* @param codec 译码器指针*/
void oc_conv_delete_codec(void * codec);/*** @brief oc_conv_reset_status 重置卷积码编译码器 codec 的判决路径,* 用于突发重置卷积译码* @param codec 卷积码编译码器*/
void oc_conv_reset_status(void *codec);/*** @brief oc_conv_encode 使用卷积码编译码器 codec 对长度为nLen时钟的各个* 输入符号 inputArray 进行编码。* @param codec 卷积码编译码器* @param inputArray 输入符号,过去时钟在高位。比如k=2时,* 第一符号 2bit为 [i=0] [i=1]* @param nLen 符号长度* @param outputArray 输出符号,n比特,过去时钟在高位,* 如第一符号 2bit 为 [i=0] [i=1]* @param InitialStatus 编码时的初态。注意,过去时钟寄存器在低位。* @return 编码完成后的下一初态,可以进行迭代连续编码。*/
int oc_conv_encode(void * codec,const int inputArray[],int nLen, int outputArray[], int InitialStatus);/*** @brief oc_conv_decode 把nSymbolLen长度的硬判决符号CodeArray压入译码队列。* @param codec 卷积码编译码器* @param CodeArray CodeArray的高n~2n-1位是删除标记,表示对应的0-n-1位是否无效。(0:有效,1:无效)* 这主要用于打孔恢复。被打孔的位置,对应高位为1.如, 1 0 0 1, 说明本符号的高位是无效的,低位是1* @param nSymbolLen 符号长度* @param bFinished 如果是最末的数据,则用 bFinished = 1进行指示。* @return 可弹出的时钟数。除非是 bFinished == 1,否则,压入 nSymbolLen 个符号只能弹出 nSymbolLen-L个符号。* 其余的符号要等到冲洗到位后才能弹出。*/
int oc_conv_decode(void * codec,const int CodeArray[], int nSymbolLen, int bFinished);/*** @brief oc_conv_decode_soft 把nBitLen长度的软判决比特CodeArray压入译码队列。* @param codec 卷积码编译码器* @param CodeArray 编码软比特* @param nBitLen 软比特个数* @param v0 0的电平,如-8192* @param v1 1的电平,如+8192* @param vOmit中间电平,如0.0* @param bFinished 如果是最末的数据,则用 bFinished = 1进行指示。* @return*/
int oc_conv_decode_soft(void * codec,const int CodeArray[], int nBitLen, int v0, int v1, int vOmit, int bFinished);/*!* \brief oc_conv_curr_best_hamming 返回当前最优路径的错误个数(汉明距离)* \param codec 卷积码编译码器* \return 硬判hamming,或软判欧式距离*/
unsigned long long oc_conv_curr_best_hamming(void * codec);/*** @brief oc_conv_pop_data 尝试从路径图中弹出nLen组信息,,每个符号放在DataArray里。* @param codec 卷积码编译码器* @param DataArray 用于存放输出时钟数据的数组* @param nLen 最大要弹出的时钟* @return 弹出时钟数*/
int oc_conv_pop_data(void * codec,int DataArray[], int nLen);
5. 演示GUI
如果安装了Qt,则可以使用图形界面演示效果。
6. 代码链接
参考
https://gitcode.net/coloreaglestdio/open_codec
可用于SDR的C语言纠错编码通用算法收集(3)-卷积码与Viterbi译码相关推荐
- 可用于SDR的C语言纠错编码通用算法收集(1)-朴素字典查表BCH纠错
在开设了SDR软件无线电课程后,有余力的学生开始尝试开发自己的小通信电台,实现Hello World的无线传输.在实验过程中,一定会遇到纠错的问题.即使是在实验室,理想化的信道也是不存在的.没有纠错的 ...
- 可用于SDR的C语言纠错编码通用算法收集(4)-LDPC低密度奇偶校验码
为了配合学生借助 taskbus进行纯控制台STDIO模块的开发,我们收集了手头的通用纠错算法,便于学生进行开发学习.今天介绍LDPC编码. 1. 低密度奇偶校验编码LDPC 低密度奇偶校验码LDPC ...
- 一步一步写算法(之通用算法的编写)
[ 声明:版权所有,欢迎转载,请勿用于商业用途. 联系信箱:feixiaoxing @163.com] 前面我们写过各种各样的算法,什么排序.查找.二叉树.队列.堆栈等等.但是我们在编写这些代码的时 ...
- c语言系统的通用数据结构,(转载)C语言实现通用数据结构的高效设计
(转载)C语言实现通用数据结构的高效设计 [复制链接] 使用宏替代模板的方案 最近在阅读一个开源的C++代码,里面用到了大量的STL里面的东西.也许是自己一直用C而很少用C++来实现算法的原因,STL ...
- 基于 Ruby 谈谈——程序设计语言的通用框架
基于 Ruby GScript 谈谈--程序设计语言的通用框架 目录 基于 Ruby GScript 谈谈--程序设计语言的通用框架 一.架构思维导图 1.Ruby GScript 介绍 2.实践: ...
- ICCV 2021| GRF: 用于三维表征和渲染的通用神经辐射场(已开源)
作者丨Bo Yang@知乎 来源丨https://zhuanlan.zhihu.com/p/399867075 编辑丨3D视觉工坊 论文链接: https://arxiv.org/abs/2010.0 ...
- C语言实现通用链表初步(一)
注意:本文讨论的是无头单向非循环链表. 假设不采用Linux内核链表的思路,怎样用C语言实现通用链表呢? 一种常用的做法是: typedef int element_t; struct node_in ...
- boost::regex模块用于测试特定于语言环境的表达式的帮助程序类
boost::regex模块用于测试特定于语言环境的表达式的帮助程序类 实现功能 C++实现代码 实现功能 boost::regex模块用于测试特定于语言环境的表达式的帮助程序类 C++实现代码 #i ...
- 制作加密狗程序_【火腿DIY】用于SDR应用程序的自定义热键键盘 | 视障人士的选择...
Christoph用于SDR应用程序的自制自定义热键键盘 上周,我在SDRplay 社交页面上看到了Christoph Jahn的精彩文章. 克里斯托夫(Christoph)制作了一个与SDRuno一 ...
最新文章
- leetcode算法题--1比特与2比特字符
- java spring框架 注解_详解Java的Spring框架中的注解的用法
- modbus3-关于Modicon Modbus Protocol和modscan32
- 配置文件*.xml中 classpath: 与 classpath*: 的区别
- leetcode双指针(python与c++)
- 支付宝ios SDK官方下载页面
- JDK8 Stream操作整理
- WEB系统技术开发方向
- 事务例子_耗时3周!7000+字的Spring事务总结来啦
- 2021世界机器人大赛— 青少年机器人设计大赛
- 英特尔驱动程序下载_如何修复英特尔计算机上的“此计算机未验证正在安装的驱动程序”...
- SECS/GEM 产品开发和介绍
- popperjs V2 之应用库 tippy.js 源码阅读
- Django之 migration 原理
- ArcEngine加载图层的五个步…
- PHP语言是什么语言及能解决当下什么问题-动态更新
- 浅谈魔兽世界的BUFF系统和阵营系统
- ASP.NET MVC里ModelState.IsValid总是true或者总是false
- 网络编程--TCP实例
- realmeq参数配置详情_realmeq参数配置-realmeq手机性能规格详情