【附代码片解释】MSK正交调制&正交解调&mskmod函数_msk csdn_卡布奇诺加勺糖的博客-程序员宅基地

技术标签: matlab  算法  代码规范  数字通信  信号处理  数字信号处理  

近在做MSK的正交调制解调仿真,有一些心得体会,也踩了一些坑,再次记录一下。
我的目标是,自己写一个正交调制解调,其性能和matlab自带的mskmod和mskdemod函数性能相近,功能相同,输出相同。

一、 mskmod函数初探

发现了一些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函数的使用方法

1. 基础用法

最基础的是直接输入需要调制的信息序列和输入上采样倍数

信息序列:输入需要是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;

2.编码

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

这里也有一个误区,有两种常见的差分编码形式,需要好好区分,用对才可以

msk需要的差分编码是如下图这种方式在这里插入图片描述

而另一种差分编码(查分曼彻斯特码)的编码方式通常用在bpsk里:

b0=1;
for i=1:N
    encode_output(i)=-b0*bitstream(i);
    b0=encode_output(i);
end  %差分编码

3.初相

初相这个我没有太多研究,一般都是设置为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路数据,两路合并的数据。
通过对比可以发现,与书上是一样的,证明我们做的结果是正确的

3. 解调

解调部分就比较简单,就是正常的正交解调

    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

写在后面

都看到这里了,我就告诉你一个小秘密,不知道你注意到了没有,代码中没有低通滤波!!

是的,我故意的,没想到吧,但是别急着骂我,上面代码仿真出来的结果肯定和理论值相同的,代码没问题,仿真没问题,结果也没问题,那是我有问题

从理论上来说,与本地载波自相关后肯定要过一个低通滤波来滤除高频项,而且我与本地载波进行了两次自相关,应该要用两个低通滤波才行,这就涉及到下面我说的两个问题了
.
.

这里有我本人也没搞清楚的两个点,希望大家一起讨论:

  1. 先说第二次自相关和第二个理论上的低通滤波:
    这里其实我是用累加来代替低通滤波了,高频项在累加过程中,sin或cos函数转了一圈,积分为0,因此变相的完成了低通滤波
  2. 第一次与本地载波自相关后我没有加入低通滤波
    其实这里我本来在代码中写了低通滤波器的,并且仿真得到的结果与理论值也是一样的,但是我突然想,如果我去掉这个低通滤波,我只用一个低通滤波可不可以?
    这里以I路的公式为例,如果第一次相关后不做低通滤波,那么高频项会一直保留,在第二次相关时产生高频。一开始我直观的想,我进行累加,第二次相关的2到5项都会积分得到零,所以仿真中我去掉了第一个低通滤波,进行仿真,结果和不进行第一次低通滤波的误码率相同。
    但是实际上,只有第二项可以在累加求和中得到零,其他项由于存在Wc频率的sin或cos,导致积分不为零,因此,从理论上讲这会影响我的误码率,但是仿真结果是正确的,而且我也考虑了Wc和Ts的倍数关系,我将两者设置成不是整倍数的数值,仿真结果也是正确的,这让我很费解
    在这里插入图片描述

有没有大佬解惑,求大佬解惑!!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_45475922/article/details/119611161

智能推荐

linux常用命令_linux addr_一户董的博客-程序员宅基地

记录工作中经常用到的Linux命令,作为备忘录,同时也希望能帮助到看到的朋友。_linux addr

Spark | 读取Hive表数据写入MySQL_点滴笔记的博客-程序员宅基地

import java.sql.Connectionimport scala.collection.mutable.ArrayBufferobject JdbcTemplateUtil extends Serializable { /** * 单条操作 * @param sql * @param params */ def executeSql(conn: Connection, sql: String, params: ...

Centos8搭建Mosquitto(三)workerman/mqtt+workerman/mysql完成MQTT订阅消息持久存储Mysql数据库_老刘307的博客-程序员宅基地

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

动态查找之二叉排序树(BST)_lansirongjiadezhu的博客-程序员宅基地

当查找表以线性表的形式组织时,若对查找表进行插入、删除或排序操作,就必须移动大量的记录,当记录数很多时,这种移动的代价很大。利用树的形式组织查找表,可以对查找表进行动态高效的查找。 二叉排序树(Binary Sort Tree或Binary Search Tree)定义:二叉排序树或者是空树,或者是满足下列性质的二叉树。(1) :若左子树不为空,则左子树上所有结点的

小量程该怎样选用超声波液位计?_小量程液位计_tt9712_的博客-程序员宅基地

大禹电子小量程的超声波液位计,一般是指2米以内量程。这个是要根据工作环境来选择:  1、如果是敞口的水池,罐子,可以不用考虑盲区问题,因为安装的时候,可以提高安装高度来解决盲区问题。  2、如果是密闭的水池,一般是在水池上开孔安装,在水池盖板上用法兰连接,这个时候尽可能选择M78×2的探头,有一种盲区可以做到0.20米。同时要求现场最好用DN150法兰连接,这样法兰下面的接管内径大,内壁不太容易产生干扰。最小要用DN100的法兰。  3、如果是密闭的罐子,都是罐子顶部有法兰连接,这个时候尽可能选_小量程液位计

随便推点

NFS服务_fns 服务禁用有什么影响_李珊.019089的博客-程序员宅基地

文章目录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环境安装搭建过程_搭建pqthon_Go__home的博客-程序员宅基地

详细的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

HDU3255[farm] (扫描线方体体积并)_silentsaber~的博客-程序员宅基地

终于调出来了,恶心死我了- -http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=19080刚恶补扫描线,做了几道入门题,然后继续找题,网上推荐了这道题,就打算来做一下。恩,题意还是比较简单,求矩形的面积*权值,重叠部分*最大权值。卧槽,好像挺简单,恩,不对呀,怎么找到最大,于是考虑暴力枚举- -挂了~~~~

Linux文件共享服务之NFS_fns共享_谢公子的博客-程序员宅基地

NFS(Network File System) 网络文件系统,是FreeBSD支持的文件系统中的一种,它允许网络中的计算机之间通过TCP/IP网络共享资源。在NFS的应用中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,就像访问本地文件一样。NFS主要用于LInux与Linux之间进行文件系统共享。简单的来说:它就是是可以透过网络,让不同的主机、不同的操作系统可以共享存储..._fns共享

ROS入门之Cmakelist说明_catkin_include_dirs_Bourne_Boom的博客-程序员宅基地

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文件、并指定编码格式_spark.read.csv_大数据翻身的博客-程序员宅基地

一、用spark实现读取csv文件核心代码:val spark = SparkSession .builder() .master("local[*]") .appName("app") .getOrCreate() //读取文件//方式一: val srcDF = spark .r_spark.read.csv

推荐文章

热门文章

相关标签