技术标签: matlab 算法 代码规范 数字通信 信号处理 数字信号处理
近在做MSK的正交调制解调仿真,有一些心得体会,也踩了一些坑,再次记录一下。
我的目标是,自己写一个正交调制解调,其性能和matlab自带的mskmod和mskdemod函数性能相近,功能相同,输出相同。
发现了一些mskmod函数的一些秘密,在此记录一下
mskmod - Minimum shift keying modulation
This MATLAB function outputs the complex envelope y of the modulation of the
message signal x using differentially encoded minimum shift keying (MSK)
modulation.y = mskmod(x,nsamp) y = mskmod(x,nsamp,dataenc) y = mskmod(x,nsamp,dataenc,ini_phase) [y,phaseout] = mskmod(...)
从help mskmod函数我们可以得到mskmod函数的使用方法
最基础的是直接输入需要调制的信息序列和输入上采样倍数
信息序列:输入需要是01序列,不能是+1/-1这种
上采样:即一个波形采样多少个点,比如一个波形时间为1s,采样率为100Hz,那么上采样倍数就是100,如果一个波形时间为2s,采样率为100Hz,上采样倍数就是50
在实际的仿真代码中,我是这样设置的:
R = 1e1; % The bit speed
Fc = 1e1; % The carrier frequency
Fs = 1e2; % the sample frequency
Ts = 1/Fs;
Upsample = Fs/R;
mskmod函数第三个参数是是否做数据差分编码,默认情况下,mskmod是’diff’方式进行调制,也就是默认情况下,mskmod函数认为你的输入是已经经过差分编码的
’nondiff’ 表示输入的数据是未经差分编码
’diff’ 表示输入的数据是经过差分编码的
但是要注意,这里有一个大坑!!!!
起初我认为,我在mskmod函数里输入diff,是让mskmod函数帮我做一个差分编码,但是仿真结果总是出错,后来我扣了扣mskmod代码注解的字眼
dataenc can be either ‘diff’ for differentially encoded MSK or ‘nondiff’ for nondifferentially encoded MSK.
这里他用的encoded,意思是已经编码过的,非常坑!!
言归正传,由于msk有相位模糊的问题,因此一般是加上差分编码,这里附上差分编码的代码:
% msk差分编码
b0=1;
for i=1:N
encode_output(i)=b0*bitstream(i);%对应bk
b0=bitstream(i);
end
这里也有一个误区,有两种常见的差分编码形式,需要好好区分,用对才可以
而另一种差分编码(查分曼彻斯特码)的编码方式通常用在bpsk里:
b0=1;
for i=1:N
encode_output(i)=-b0*bitstream(i);
b0=encode_output(i);
end %差分编码
初相这个我没有太多研究,一般都是设置为0,默认也是0
经过对mskmod函数的探索,我将mskmod配置到我需要的形式,然后进行正交调制,我的目标是,正交调制的输出和mskmod函数输出相同,参考的是书上这段表格
mskmod函数代码为:
tx_data = mskmod(encode_output1,Upsample,'diff',0*pi);
正交调制的代码为:
I=[];Q=[];%奇数进I路、偶数进Q路
bitstream3 = [bitstream1(1),bitstream1];
I = bitstream3(1:2:length(bitstream3));
Q = bitstream3(2:2:length(bitstream3));
%调制
I_data=[];Q_data=[];
base_wave=-1/R:1/Fs:1/R-1/Fs;
% I_data = kron(I,cos(pi*base_wave/(2*T)));
n = -Upsample:length(I)*2*Upsample-Upsample-1;
cos_I = cos(pi*n*Ts*R./2);
I_data_temp = kron(I,ones(1,2*Upsample)).* cos_I;
I_data = I_data_temp(Upsample+1:end+Upsample*(mod(N,2)-1));%取出零时刻的值
%此处考虑了总共有奇数个bit需要传输的情况,实际上不需要考虑
% Q_data = kron(Q,sin(pi*base_wave/(2*R)));
n = 0:length(Q)*2*Upsample-1;
sin_Q = sin(pi*n*Ts*R./2);
Q_data_temp = kron(Q,ones(1,2*Upsample)) .* sin_Q;
Q_data = Q_data_temp(1:end-Upsample*mod(N,2));%%取出零时刻的值
%此处考虑了总共有奇数个bit需要传输的情况,实际上不需要考虑
% Q路延迟半个符号
number_delay=length(base_wave)/2;
% Q_data1=[zeros(1,number_delay),Q_data(1:length(Q_data)-number_delay)];
% 载波信息
bit_t=0:1/Fs:N/R-1/Fs;
I_carrier=cos(2*pi*Fc*bit_t);
Q_carrier=sin(2*pi*Fc*bit_t);
% 调制
I_band = I_data.*I_carrier;
Q_band = Q_data.*Q_carrier;
% 两路合成一路
MSK_signal = I_band + 1i*Q_band;
其正交调制框图参考书上的结构:
最终得到的波形图为
从上到下依次是,i路数据,q路数据,两路合并的数据。
通过对比可以发现,与书上是一样的,证明我们做的结果是正确的
解调部分就比较简单,就是正常的正交解调
I_output = MSK_receive.*I_carrier;
I_filter_ouput_delay=[I_output(number_delay+1:end) zeros(1,number_delay)];
Q_output = MSK_receive.*Q_carrier;
这里需要对i路进行一个补偿和延迟,这是因为:
仔细观察书上的那个表格,其实要传输的序列应该是从0时刻开始,而-2和-1时刻是人为补偿的数据,为了能够进行差分编码,因此i路传输的数据其实是6个数据,前面包含了一个自己设置的1,而这个1与我们的数据没有关系,因此在解调是应该去除半个周期的头部,但是这样操作会使得i路数据变短了,无法解调出最后一个数据,因为缺了半个周期,因此我们这里进行补零,补零不影响数据的解调,只是补足了缺少的数据。
解调时,先进行一次本地相关,然后积分判决就可以得到iq数据,代码如下:
%第二次本地相关解调
I_dected = real(I_filter_ouput_delay).*cos_I(2*number_delay+1:end);
Q_dected = imag(Q_output).*sin_Q;
%积分判决
for i=1:round(N/2)
I_recover(i) = sign(sum(I_dected((i-1)*2*number_delay+1:i*2*number_delay)));
Q_recover(i) = sign(sum(Q_dected((i-1)*2*number_delay+1:i*2*number_delay)));
end
% 合并IQ两路
bit_recover = zeros(1,N);
bit_recover(1:2:end) = Q_recover;
bit_recover(2:2:end) = I_recover;
bit_recover = (bit_recover+1) / 2;
至此,我们完成了所有的代码,可以比对数据得到误码率了
仿真得到的误码率曲线为:
看到,仿真曲线与理论曲线基本一致,因此实现了我们的目标
%%%%%%%%%%%%%%%%%%%%% MSK调制解调 %%%%%%%%%
%%%%% data:2021年8月10日 author:卡布基诺加勺糖 %%%%%%
%%%%%程序说明
%MSK调制解调器的仿真
%调制方式:MSK 编码:差分编码(正交调制等效于差分编码??)
%噪声类型:线性高斯白噪声
%解调解调方式:采用正交调制、相干解调
%%%%%仿真环境
% 软件版本:MATLAB2019a
%% %%%%%%%%%%%%%%%%% 参数设定 %%%%%%%%%%%%%%%%%%%%%%
clc;clear all;
tic
R = 1e1; % The bit speed
Fc = 1e1; % The carrier frequency
Fs = 1e2; % the sample frequency
Ts = 1/Fs;
Upsample = Fs/R;
%% %%%%%%%%%%%%%%%%% 主程序 %%%%%%%%%%%%%%%%%%%%%%
EbN0_loop = [0 2 4 6 8 ] ; % signal to noise rate
N_loop = [1e4 5e4 1e5 5e5 1e6 ]; %循环点数
for loop=1:length(EbN0_loop)
%%%%%%%%%%%%%%%%%%% 信源 %%%%%%%%%%%%%%%%%%%%%%
% loop=1; % use for debug
% 生成信息序列
N = N_loop(loop);
bitstream=randi([0,1],1,N); % randomly generate the bitstream
bitstream = [1 1 1 0 0 1 0 1 1 0];
bitstream1=2*bitstream-1; % 双极性
%%%%%%%%%%%%%%%%%%% 编码 %%%%%%%%%%%%%%%%%%%%%%
% b0=1;
% for i=1:N
% encode_output(i)=-b0*bitstream1(i);
% b0=encode_output(i);
% end %dpsk差分编码
b0=1;
for i=1:N
encode_output(i)=b0*bitstream1(i);%对应bk
b0=bitstream1(i);
end % msk差分编码
%%%%%%%%%%%%%%%%%%% 调制 %%%%%%%%%%%%%%%%%%%%%%
% % 用matlab已有函数mskmod进行调制
% encode_output1 = (encode_output + 1)/2;
% tx_data = mskmod(encode_output1,Upsample,'diff',0*pi);
% % mskmod: the elements of x must be 0 or 1
% 自己写的正交调制
I=[];Q=[];%奇数进I路、偶数进Q路
bitstream3 = [bitstream1(1),bitstream1];
I = bitstream3(1:2:length(bitstream3));
Q = bitstream3(2:2:length(bitstream3));
%调制
I_data=[];Q_data=[];
base_wave=-1/R:1/Fs:1/R-1/Fs;
n = -Upsample:length(I)*2*Upsample-Upsample-1;
cos_I = cos(pi*n*Ts*R./2);
I_data_temp = kron(I,ones(1,2*Upsample)).* cos_I;
I_data = I_data_temp(Upsample+1:end+Upsample*(mod(N,2)-1));%取出零时刻的值
%此处考虑了总共有奇数个bit需要传输的情况,实际上不需要考虑
n = 0:length(Q)*2*Upsample-1;
sin_Q = sin(pi*n*Ts*R./2);
Q_data_temp = kron(Q,ones(1,2*Upsample)) .* sin_Q;
Q_data = Q_data_temp(1:end-Upsample*mod(N,2));%%取出零时刻的值
%此处考虑了总共有奇数个bit需要传输的情况,实际上不需要考虑
% Q路延迟半个符号
number_delay=length(base_wave)/2;
% 载波信息
bit_t=0:1/Fs:N/R-1/Fs;
I_carrier=cos(2*pi*Fc*bit_t);
Q_carrier=sin(2*pi*Fc*bit_t);
% 调制
I_band = I_data.*I_carrier;
Q_band = Q_data.*Q_carrier;
% 两路合成一路
MSK_signal = I_band + 1i*Q_band;
%%%%%%%%%%%%%%%%%%%%%%% 信道 %%%%%%%%%%%%%%%%%%%%%%%
% 加入噪声
EbN0 = EbN0_loop(loop);
snr = EbN0 - 10*log10(Upsample);
MSK_receive = awgn(MSK_signal,snr,'measured');
%%%%%%%%%%%%%%%%%%% 解调 %%%%%%%%%%%%%%%%%%%%%%
I_output = MSK_receive.*I_carrier;
I_filter_ouput_delay=[I_output(number_delay+1:end) zeros(1,number_delay)];
I_dected = real(I_filter_ouput_delay).*cos_I(2*number_delay+1:end);
Q_output = MSK_receive.*Q_carrier;
Q_dected = imag(Q_output).*sin_Q;
for i=1:round(N/2)
I_recover(i) = sign(sum(I_dected((i-1)*2*number_delay+1:i*2*number_delay)));
Q_recover(i) = sign(sum(Q_dected((i-1)*2*number_delay+1:i*2*number_delay)));
end
bit_recover = zeros(1,N);
bit_recover(1:2:end) = Q_recover;
bit_recover(2:2:end) = I_recover;
bit_recover = (bit_recover+1) / 2;
error_rate(loop) = sum(abs(bit_recover - bitstream)) / N ;
end
%%%%%%%%%%%%%%%%%画图%%%%%%%%%%%%%%%%%
%save('error_rate.mat','error_rate');
toc
disp('save success')
figure()
ber_standard = berawgn(EbN0_loop,'msk','on','coherent');
semilogy(EbN0_loop,ber_standard);
hold on
semilogy(EbN0_loop,error_rate);
legend('参考误码率','仿真误码率')
grid on
都看到这里了,我就告诉你一个小秘密,不知道你注意到了没有,代码中没有低通滤波!!
是的,我故意的,没想到吧,但是别急着骂我,上面代码仿真出来的结果肯定和理论值相同的,代码没问题,仿真没问题,结果也没问题,那是我有问题 ?
从理论上来说,与本地载波自相关后肯定要过一个低通滤波来滤除高频项,而且我与本地载波进行了两次自相关,应该要用两个低通滤波才行,这就涉及到下面我说的两个问题了
.
.
这里有我本人也没搞清楚的两个点,希望大家一起讨论:
记录工作中经常用到的Linux命令,作为备忘录,同时也希望能帮助到看到的朋友。_linux addr
import java.sql.Connectionimport scala.collection.mutable.ArrayBufferobject JdbcTemplateUtil extends Serializable { /** * 单条操作 * @param sql * @param params */ def executeSql(conn: Connection, sql: String, params: ...
Workerman是什么?Workerman是一款纯PHP开发的开源高性能的PHP socket 服务框架。Workerman不是重复造轮子,它不是一个MVC框架,而是一个更底层更通用的socket服务框架,你可以用它开发tcp代理、梯子代理、做游戏服务器、邮件服务器、ftp服务器、甚至开发一个php版本的redis、php版本的数据库、php版本的nginx、php版本的php-fpm等等。Workerman可以说是PHP领域的一次创新,让开发者彻底摆脱了PHP只能做WEB的束缚。实际上Worker_workerman/mqtt
当查找表以线性表的形式组织时,若对查找表进行插入、删除或排序操作,就必须移动大量的记录,当记录数很多时,这种移动的代价很大。利用树的形式组织查找表,可以对查找表进行动态高效的查找。 二叉排序树(Binary Sort Tree或Binary Search Tree)定义:二叉排序树或者是空树,或者是满足下列性质的二叉树。(1) :若左子树不为空,则左子树上所有结点的
大禹电子小量程的超声波液位计,一般是指2米以内量程。这个是要根据工作环境来选择: 1、如果是敞口的水池,罐子,可以不用考虑盲区问题,因为安装的时候,可以提高安装高度来解决盲区问题。 2、如果是密闭的水池,一般是在水池上开孔安装,在水池盖板上用法兰连接,这个时候尽可能选择M78×2的探头,有一种盲区可以做到0.20米。同时要求现场最好用DN150法兰连接,这样法兰下面的接管内径大,内壁不太容易产生干扰。最小要用DN100的法兰。 3、如果是密闭的罐子,都是罐子顶部有法兰连接,这个时候尽可能选_小量程液位计
full-text中两个关键因素:analyzed和relevence
文章目录1. nfs介绍1.1nfs特点1.2使用nfs的好处1.3nfs的体系组成1.4nfs的应用场景2.nfs工作机制2.1RPC2.2 NIS2.3 nfs工作机制3.nfs工作机制4.nfs管理5.案例1. nfs介绍1.1nfs特点nfs功能:把本机的资源(目录)共享给另一台主机挂载使用NFS(Network File System)即网络文件系统,是FreeBSD支持的文件..._fns 服务禁用有什么影响
详细的python环境安装搭建过程1、学python先要下载什么?2、搭建Python环境2.1官网安装2.1.1下载安装包2.2.2python的安装2.2用anaconda进行安装2.2.1下载anaconda2.2.2anaconda的安装3、pycharm的安装以及使用3.1、安装pycharm3. 2、pycharm的使用4、怎么安装第三方库?1、学python先要下载什么?pyt..._搭建pqthon
终于调出来了,恶心死我了- -http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=19080刚恶补扫描线,做了几道入门题,然后继续找题,网上推荐了这道题,就打算来做一下。恩,题意还是比较简单,求矩形的面积*权值,重叠部分*最大权值。卧槽,好像挺简单,恩,不对呀,怎么找到最大,于是考虑暴力枚举- -挂了~~~~
NFS(Network File System) 网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源。在NFS的应用中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,就像访问本地文件一样。NFS主要用于LInux与Linux之间进行文件系统共享。简单的来说:它就是是可以透过网络,让不同的主机、不同的操作系统可以共享存储..._fns共享
Cmakelisthttp://wiki.ros.org/catkin/CMakeLists.txt1 Overall Structure and OrderingYour CMakeLists.txt file MUST follow this format otherwise your packages will not build correctly. The order in ..._catkin_include_dirs
一、用spark实现读取csv文件核心代码:val spark = SparkSession .builder() .master("local[*]") .appName("app") .getOrCreate() //读取文件//方式一: val srcDF = spark .r_spark.read.csv