世界的本质是旋转(7) 野路子PSK 接收机上层同步的技巧与缺陷

上一篇文章里,我们以BPSK为例子,介绍了nPSK(n=2,4,8)波形的接收、解调中的同步技术。

前文阐述的同步技术所工作的对象是复平面的坐标,X轴是实部、Y轴是虚部。当完成时钟、频率同步后,就获得了一串整数,也就是解调的结果了。但还有很多其他的工作有待完成。调制与解调只是协议栈最底层的部分。本节,会继续介绍码流层面的同步技术。同时,在文章的最后,会给出这种野路子协议栈的缺陷,以及学习通信原理时需要具备的认知:书本的流程和现实实验之间存在大量的技巧知识空隙,需要仔细琢磨和学习思考。

1. 选取较好的符号映射方案

对BPSK来说,符号取值0,1;QPSK为0,1,2,3;8PSK为0~7的整数。

在调制阶段,如何把这些整数填写到复平面,是有一定讲究的。主要的原则有两个:

  1. 0直流分量。各个符号如果是均匀出现的,则其重心应位于原点。含有直流分量的方案在经过各级数字、模拟处理后,直流分量会丢失或者导致畸变,从电气角度也是不好的。
  2. 相邻位置01跳变最小。在复平面上相邻的位置是比较容易出错的。因此要把二进制长相类似的符号作为邻居,而不只是算术相邻的。比如 1和2算术相邻,但是二进制相差2比特,01,10,如果相邻判错符号,一下就引入2bit错误。因此1最好与0、3相邻。

采用上述原则的映射,就是最常见的Gray映射。

	static const int sbm_qpsk[4][2] = {{1,1},{1,-1},{-1,1},{-1,-1}};
	static const double sbm_8psk[8][2] = {
		{cos(c_pi * 0 /4),sin(c_pi * 0 /4)},//000,0
		{cos(c_pi * 1 /4),sin(c_pi * 1 /4)},//001,1
		{cos(c_pi * 3 /4),sin(c_pi * 3 /4)},//010,2
		{cos(c_pi * 2 /4),sin(c_pi * 2 /4)},//011,3
		{cos(c_pi * 7 /4),sin(c_pi * 7 /4)},//100,4
		{cos(c_pi * 6 /4),sin(c_pi * 6 /4)},//101,5
		{cos(c_pi * 4 /4),sin(c_pi * 4 /4)},//110,6
		{cos(c_pi * 5 /4),sin(c_pi * 5 /4)} //111,7
	};

行Q列I-11
120
-131
clc;
clear;
theta = 0:pi/4:pi*2-pi/4;
r = ones(1,8);
polar(theta,r,'r*');

sbcode = [
		cos(pi * 0 /4),sin(pi * 0 /4);
		cos(pi * 1 /4),sin(pi * 1 /4);
		cos(pi * 3 /4),sin(pi * 3 /4);
		cos(pi * 2 /4),sin(pi * 2 /4);
		cos(pi * 7 /4),sin(pi * 7 /4);
		cos(pi * 6 /4),sin(pi * 6 /4);
		cos(pi * 4 /4),sin(pi * 4 /4);
		cos(pi * 5 /4),sin(pi * 5 /4)];

textitmBIN = ['000';'001';'010';'011';'100';'101';'110';'111'];
textitmDEC = ['0';'1';'2';'3';'4';'5';'6';'7'];
text(sbcode(:,1)*0.9,sbcode(:,2)*0.9,textitmBIN,'color','blue');
text(sbcode(:,1)*0.8,sbcode(:,2)*0.8,textitmDEC,'color','red');

8psk可以看到,上图的复平面位置(红色星号)相邻位置上仅反转1个比特。

2 接收相位模糊处理

由于我们的锁相环路锁定的是相对的位置,即随便选取一个初始相位为参考0,进行同步,故而接收到的复平面坐标极有可能是整体旋转一个角度的。旋转的方案总共就n种。对BPSK,只有2种,QP4种,8P8种。

如果在解调时,直接按照没有旋转的情况已经做了判决,则可以通过代换表的方法来方便获取旋转后的方案。代换的思路是“旋转后,目前认为是m的位置,实际应该是n”。生成代换表的8PSK Octave程序:

code = [0 1 3 2 6 7 5 4];
rota = [0 1 3 2 6 7 5 4];
mapt = [0 0 0 0 0 0 0 0];
for turn_idx = 1:8
  mapt(code+1) = rota;
  disp(mapt);
  tmp = rota(1);
  rota(1:7) = rota(2:8);
  rota(8) = tmp;
end

输出:

   0   1   2   3   4   5   6   7
   1   3   6   2   0   4   7   5
   3   2   7   6   1   0   5   4
   2   6   5   7   3   1   4   0
   6   7   4   5   2   3   0   1
   7   5   0   4   6   2   1   3
   5   4   1   0   7   6   3   2
   4   0   3   1   5   7   2   6

整理:

	static const int turnningMap[8][8] = {
		{0,1,2,3,4,5,6,7},
		{1,3,6,2,0,4,7,5},
		{3,2,7,6,1,0,5,4},
		{2,6,5,7,3,1,4,0},
		{6,7,4,5,2,3,0,1},
		{7,5,0,4,6,2,1,3},
		{5,4,1,0,7,6,3,2},
		{4,0,3,1,5,7,2,6}
	};

在使用上表时,在模糊方案mis下,按照方案0判定的符号为p,则恢复后的符号为 turnningMap[mis][p];

怎么判断哪一种旋转方案是正确的呢?

一种方法是加入一种固定的头部,如果旋转后的序列里能够找到这个头部,就对了。但加入固定头部会带来更多的开销,降低有效比特率。另一种方法是利用纠错序列的校验进行同步。本例子就是利用校验关系进行同步。

由于采用的是标准的无限长卷积纠错码,因而使用校验序列即可完成验证。校验方法参考:http://staff.ustc.edu.cn/~wyzhou/ct_chapter4.pdf,这篇中科大的论文里,通过一种局部校验矩阵实现判定。如果恢复对了,则使用校验矩阵滑动一个窗口,滑动生成的所有校验值都是0。

对于构造校验矩阵,即可以使用论文的方法,也可以使用穷尽法。满足校验关系的方案其实很多。通过下面的小程序,找一个参与校验位数最多的方案即可:

int main()
{
	srand(time(0));
	unsigned char reg[6] = { 0,0,0,0,0,0 };
	std::vector<unsigned char> vec_info;
	const int testL = 1000;
	//产生长度1000的随机编码,1/3系统卷积码,为了和8psk符号长度3对应,取1/3利于对齐
	//100,133,171
	for (int i = 0; i < testL; ++i)
	{
			const int c = rand()%2;
			int c1 = c ^ reg[1] ^ reg[2] ^ reg[4] ^ reg[5];
			int c2 = c ^ reg[0] ^ reg[1] ^ reg[2] ^ reg[5];
			vec_info.push_back(c*4 + c1 * 2 + c2);
			reg[5] = reg[4];
			reg[4] = reg[3];
			reg[3] = reg[2];
			reg[2] = reg[1];
			reg[1] = reg[0];
			reg[0] = c;
		
	}
	//称重查表,下标 0-7的二进制含有的1的个数
	const int spopcnt[] = { 0,1,1,2,1,2,2,3 };
	int maxWeight = 0;
	//穷尽校验式,2^21种
	for (int v = 0; v < (1 << (3 * 7)); ++v)
	{
		int val[7] = {(v&7),((v>>3) & 7),((v >> 6) & 7),((v >> 9) & 7),
		((v >> 12) & 7),((v >> 15) & 7),((v >> 18) & 7) };
			int ch = 0;
			for (int i = 0; i < testL - 7; i += 1)
			{
				for (int j = 0; j < 7; ++j)
					ch += spopcnt[(vec_info[i + j] & val[j])];
				if (ch % 2)
					break;
			}
			if (ch % 2 == 0)
			{
				if (maxWeight <= ch)
				{
					maxWeight = ch;
					printf("重量%d: ", ch);
					for (int j = 0; j < 7; ++j)
						printf("%d ", val[j]);
					printf("\n");
				}
				ch = 0;
			}

	}
	return 0;
}

输出:

重量0: 0 0 0 0 0 0 0
重量4002: 7 5 4 3 0 0 0
重量4002: 0 7 5 4 3 0 0
重量4940: 7 2 1 7 3 0 0
重量4960: 3 6 6 5 6 0 0
重量4962: 7 1 3 1 1 3 0
重量5938: 7 6 6 5 2 3 0
重量5970: 7 5 3 6 4 3 0
重量6010: 4 7 5 4 7 3 0
重量6012: 7 2 5 7 4 5 0
重量6990: 3 5 7 6 7 5 0
重量6990: 7 6 5 7 4 1 3
重量7906: 7 6 6 2 7 7 3
重量7928: 3 6 1 7 7 7 3
重量8018: 7 5 7 5 6 5 6

如此一来,只要在代码中不断检查这个校验关系,则可以找到正确的旋转方案了。

主要代码参考:

std::vector<std::vector<unsigned char> > alg_decap_8psk(const std::vector<unsigned char> & packages,void * codec)
{
	//0. Append to cache
	const  unsigned char * pSyms = (const  unsigned char *)packages.data();
	const  int sym_total = packages.size();
	for (int i=0;i<sym_total;++i)
	{
		cache_symbols[w_clk % RingBufSize]=pSyms[i];
		++w_clk;
	}
	//1. 测试相位旋转,使用http://staff.ustc.edu.cn/~wyzhou/ct_chapter4.pdf 的H校验方法
	const unsigned char test_Hq[7]{7, 5,7,5,6,5,6};
	if (r_clk + 128 > w_clk)
		return res;
	const int symbol_len = w_clk - r_clk - 2;
	static int oppo01 = 0;
	int good_times[8]{0,0,0,0,0,0,0,0};
	static const int turnningMap[8][8] = {
		{0,1,2,3,4,5,6,7},
		{1,3,6,2,0,4,7,5},
		{3,2,7,6,1,0,5,4},
		{2,6,5,7,3,1,4,0},
		{6,7,4,5,2,3,0,1},
		{7,5,0,4,6,2,1,3},
		{5,4,1,0,7,6,3,2},
		{4,0,3,1,5,7,2,6}
	};
	static int bestMis = 0,bestGood = 0;
	bestGood = 0;
	int spopcnt[8]={0,1,1,2,1,2,2,3};
	for (int pos = 0; pos < 128-7; ++pos)
	{
		for (int mis = 0;mis<4;++mis)
		{
			int chk =
				spopcnt[(turnningMap[mis][cache_symbols[(r_clk + pos + 0) % RingBufSize ]] & test_Hq[0] )%8 ]+
				spopcnt[(turnningMap[mis][cache_symbols[(r_clk + pos + 1) % RingBufSize ]] & test_Hq[1] )%8 ] +
				spopcnt[(turnningMap[mis][cache_symbols[(r_clk + pos + 2) % RingBufSize ]] & test_Hq[2] )%8 ] +
				spopcnt[(turnningMap[mis][cache_symbols[(r_clk + pos + 3) % RingBufSize ]] & test_Hq[3] )%8 ] +
				spopcnt[(turnningMap[mis][cache_symbols[(r_clk + pos + 4) % RingBufSize ]] & test_Hq[4] )%8 ] +
				spopcnt[(turnningMap[mis][cache_symbols[(r_clk + pos + 5) % RingBufSize ]] & test_Hq[5] )%8 ] +
				spopcnt[(turnningMap[mis][cache_symbols[(r_clk + pos + 6) % RingBufSize ]] & test_Hq[6] )%8 ] ;
			good_times[mis] += (chk%2) ? 0:1;
		}
	}
	//mis
	for (int i=0;i<8;++i)
	{
		if (bestGood < good_times[i])
		{
			bestGood = good_times[i];
			bestMis = i;
		}
	}
	//...
}

3 信息流的封装与处理

如何把具有不同长度的字节流,比如我们的以太网包,封装为一个连续的流呢?可以参考古老的HDLC封装方法。对于一个流,遇到连续的5个1就插入一个0.这样,采用对称序列01111110即可分割各个序列了。

在恢复时,

  1. 遇到01111110就处理缓存
  2. 当前缓存,累加到5个1,判断后面如果是0,则踢出一个。
  3. 如果有连续7个1出现,那就是错误了。提示反码或者质量太差。

相关封装代码:(解封装相反操作即可)

	//Checksum
	std::vector<unsigned char> input;
	std::copy(inputData.begin(),inputData.end(),std::back_inserter(input));
	unsigned int vsum = 0;
	unsigned char * psum = (unsigned char *) &vsum;
	for (int i=0;i<input.size();++i)
	{
		unsigned int vl = (vsum >>24) &0xff;
		unsigned int vr = input[i] ^ vl;
		vsum <<=8;
		vsum ^=vr;
	}
	input.push_back(psum[0]);
	input.push_back(psum[1]);
	input.push_back(psum[2]);
	input.push_back(psum[3]);

	unsigned long long len = input.size();
	//Bit encap using fake HDLC,校验是野路子的。不是标准HDLC
	std::vector<unsigned char> bitspan{0,1,1,1,1,1,1,0};
	int cnt = 0;
	for (int i=0;i<len;++i)
	{
		for (int j=0;j<8;++j)
		{
			const int c = (input[i]>>j)&0x01;
			bitspan.push_back(c);
			if (c)
			{
				++cnt;
				if (cnt==5)
				{
					bitspan.push_back(0);
					cnt = 0;
				}
			}
			else
				cnt = 0;
		}
	}

4 野路子解调的缺陷

在室内载噪比较高的情况下, 8PSK能够满足传输的需要,但这种野路子的锁相环在8PSK下还是有一定的误码。要收敛星座,需要在 4倍采样率下,进行最佳位置拟合,用非最佳状态的四个点,拟合出最佳的位置。拟合对于BPSK、QPSK这种相位差达到90度、180度的方式不是很重要(信号强),但8PSK还是很重要的。

filter上图中,红色的四倍采样位置与黑色的理论位置没有重合,无论如何选取4路中的一路,得到的向量点都是不聚焦的,会影响质量。要更好地计算实际的黑色位置,需要额外的计算量和算法。

代码参考:

https://gitcode.net/coloreaglestdio/taskbus_course/-/tree/master/src

8PSK
这个实验的意义在于让我们意识到,从课本上的原理图,到实际能够工作的完整协议栈之间,存在巨大的知识间隙。一旦脱离了仿真,变成SDR真刀真枪的干上了,需要解决的技术细节很多很多。

更为完善的解调应该同步准确的星座位置,并避免多径导致的衰落。这些工作只有结合具体的场景和波形的特点来做了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/450769.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

sqlserver中将csv非空间数据(带点坐标)转为空间数据

1、导入csv数据 2、修改字段shape为空间字段 ALTER TABLE FJPOIHB66 ALTER COLUMN shape geometry;3、空间字段转字符串 UPDATE FJPOIHB66 SET shape geometry::STGeomFromText(CONVERT(nvarchar(254),shape), 4326);4、设置主键字段 5、即可

Instagram被封了?Ins封号的6个常见原因及防封技巧

现在&#xff0c;Instagram 对于跨境电商和社交媒体营销人员来说十分重要。然而&#xff0c;许多用户发现他们的Instagram刚注册就被封&#xff0c;大家要知道 Instagram 和 Facebook 等其他平台一样&#xff0c;对账户管理的管控机制非常严格&#xff0c;不过&#xff0c;Inst…

nut-ui组件库icon中使用阿里图标

1.需求 基本每个移动端组件库都有组件 icon组件 图标组件、 但是很多组件库中并找不到我们需要的图标 这时候 大家有可能会找图标库 最大众的就是iconfont的图标了 2.使用 有很多方式去使用这个东西 比如将再限链接中的css引入 在使用 直接下载图标 symbol 方式 等....…

【群环域】多项式环基础

目录 一. 多项式环的基本定义 二. 环与多项式环 三. 多项式环的性质 四. 多项式环的次数&#xff08;degree&#xff09; 五. 多变量多项式 六. 多变量多项式环R的同态 一. 多项式环的基本定义 令R代表环&#xff08;Ring&#xff09;&#xff0c;多项式环中x对应的系数…

【C语言】自定义类型:结构体

1. 结构体类型的声明 1.1 结构体回顾 结构是⼀些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 1.1.1 结构的声明 struct tag {member-list; }variable-list; 例如描述⼀个学⽣&#xff1a; struct Stu {char name[20];//名字int age;//年…

【C语言】strcpy函数的超细节详解(什么是strcpy,如何模拟实现strcpy?)

目录 一、观察strcpy()库函数的功能与实现 二、模仿实现strcpy()函数 &#x1f50d;优化代码 &#x1f50d;assert断言拦截 &#x1f50d;const修饰常量指针 &#x1f50d;返回值的加入 三、共勉 一、观察strcpy()库函数的功能与实现 首先我们先来观察一下库函数strcpy去实现…

forward请求转发、include请求转发

forward请求转发&#xff0c;执行到这里就请求转发去执行Servlet4 include请求转发在执行完Servlet4之后&#xff0c;还会回来执行完。

五大模型大比拼:Claude3、Gemini、Sora、GPTs与GPT-4的优缺点分析

课程安排 学习内容 第一章 2024年AI领域最新技术 1.OpenAI新模型-GPT-5 2.谷歌新模型-Gemini Ultra 3.Meta新模型-LLama3 4.科大讯飞-星火认知 5.百度-文心一言 6.MoonshotAI-Kimi 7.智谱AI-GLM-4 第二章 OpenAI开发者大会后GPT最新技术 1.最新大模型GPT-4 Turbo详细介绍…

鞋服品牌如何计算门店盈亏平衡?

在鞋服品牌的运营中&#xff0c;门店盈亏平衡是衡量门店经营效果的重要指标。盈亏平衡点意味着门店在达到这一销售水平时&#xff0c;既能够覆盖所有固定和变动成本&#xff0c;又能实现零利润或零亏损。计算门店盈亏平衡有助于品牌更好地理解门店的经营状况&#xff0c;制定合…

解决LangChain构建知识向量库的过程中官方API无法自定义文本切割方式的问题-例如按行切分

自定义切分构成知识向量库的文本o(&#xffe3;▽&#xffe3;)ブ 在使用大模型和知识向量库进行问题问答的过程中&#xff0c;由于一些LangChain切分文本功能上的限制影响了模型的回答效果为了解决该问题故诞生此文档&#xff0c;如果有说的不对的&#xff0c;或者想交流的非…

Vue3中computed、watch、watchEffect的区别

三者都是侦听工具&#xff0c;实现的是观察者模式&#xff0c;横向对比 &#xff08;1&#xff09;依赖&#xff1a;指的是响应性依赖&#xff0c;也就是侦听 ref、reactive 这类具有响应性的对象。 &#xff08;2&#xff09;watch&#xff1a;默认情况下&#xff0c;被侦听对…

Spring AOP常见面试题

目录 一、对于AOP的理解 二、Spring是如何实现AOP的 1、execution表达式 2、annotation 3、基于Spring API&#xff0c;通过xml配置的方式。 4、基于代理实现 三、Spring AOP的实现原理 四、Spring是如何选择使用哪种动态代理 1、Spring Framework 2、Spring Boot 五…

CUDA入门之统一内存

原文来自CUDA 编程入门之统一内存 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;高性能&#xff08;HPC&#xff09;开发基础教程 &#x1f380;CSDN主页 发狂的小花 &#x1f304;人生秘诀&#xff1a;学习的本质…

X64 页表结构

PML4&#xff08;Page Map Level 4&#xff09;是x86-64架构中用于管理虚拟内存地址翻译的四级页表结构之一。它是一种树形结构&#xff0c;由多个页目录表&#xff08;Page Directory Pointer Table&#xff0c;PDPT&#xff09;组成&#xff0c;每个PDPT有512个指向下一级页表…

低功耗DC-DC电压调整器IU5528D

IU5528D是一款超微小型,超低功耗,高效率,升降压一体DC-DC调整器。适用于双节,三节干电池或者单节锂电池的应用场景。可以有效的延长电池的使用时间。IU5528D由电流模PWM控制环路&#xff0c;误差放大器&#xff0c;比较器和功率开关等模块组成。该芯片可在较宽负载范围内高效稳…

1_springboot_shiro_jwt_多端认证鉴权_Shiro入门

1. Shiro简介 Shiro 是 Java 的一个安全框架&#xff0c;它相对比较简单。主要特性&#xff1a; Authentication&#xff08;认证&#xff09;&#xff1a;用户身份识别&#xff0c;通常被称为用户“登录”&#xff0c;即 “你是谁”Authorization&#xff08;授权&#xff…

webpack-dev-server5.0+ 版本问题

webpack-dev-server版本选择 在使用webpack-dev-server搭建新项目时&#xff0c;需要依赖node 和webpack以及webpack-cli 这是需要注意各个应用之间的版本问题 通过npm官网查看webpack-dev-server使用的版本依赖对象 先看package.json&#xff0c;可以看到当前的版本 再找到依…

YOLOv9使用训练好的权重检测目标

打开yolov9-main\detect.py文件 1修改为训练后权重文件的位置 2改为要检测图片的位置 3修改成数据集的yaml文件 运行detect.py文件并解决报错 打开报错文件yolov9-main\utils\general.py,在prediction = prediction[0]后边加上[0] 继续运行detect.py,成功检测 存在问题 当…

9个免费游戏后端平台

在这篇文章中&#xff0c;您将看到 九个免费的游戏服务平台提供商&#xff0c;这可以帮助您开始在线多人游戏&#xff0c;而无需预先投入大量资金。 每个提供商都有非常独特的功能&#xff0c;因此成本应该只是决定时要考虑的方面之一。 我还从低预算项目的角度对免费提供商进…

如何在Ubuntu系统部署DbGate数据库管理工具并结合cpolar内网穿透远程访问

文章目录 1. 安装Docker2. 使用Docker拉取DbGate镜像3. 创建并启动DbGate容器4. 本地连接测试5. 公网远程访问本地DbGate容器5.1 内网穿透工具安装5.2 创建远程连接公网地址5.3 使用固定公网地址远程访问 本文主要介绍如何在Linux Ubuntu系统中使用Docker部署DbGate数据库管理工…