文章目录
https://github.com/liu-zongxi/OFDM_simulation
代码参考了https://zhuanlan.zhihu.com/p/385096476陈老湿的仿真,但各个函数我都有重新实现,希望写的更规范一些
该部分代码来自工程的OFDM_awgn.m
整个OFDM经历
生成一帧 → \rightarrow →串并转换 → \rightarrow →调制 → \rightarrow →ifftshift → \rightarrow →ifft → \rightarrow →添加CP → \rightarrow →并串转换 → \rightarrow →计算能量,信噪比,添加AWGN → \rightarrow →接收端串并转换 → \rightarrow →去CP → \rightarrow →FFT → \rightarrow →FFTshift → \rightarrow →解调 → \rightarrow →并串转换
%-----------------------仿真OFDM---------------------------%
%-----------------------author:lzx-------------------------%
%% 设置参数
clear;clc;
Nk = 128; % 子载波个数
Nfft = 128; % fft长度
Nframe = 6; % 一帧中有几个OFDM符号
M = 2; % 调制符号所含比特
SR = 250000; % 符号速率
BR = SR .* M; % 比特率
NGI = 32; % 保护间隔长度
EbN0s = 3:1:10; % 信噪比
Nsym = Nfft+NGI; % 系统长度
bers = zeros(1,length(EbN0s));
fprintf('EbN0 \t \t ber\t\t\t per\t\t\t nloop \t\t \n');
%% 函数主体
for kk = 1:length(EbN0s)
% rng('default') % 初始化随机种子
EbN0 = EbN0s(kk);
nloop = 10000; % 发送多少帧
n_biterror = 0; % 错误的数据
n_bitdata = 0; % 一共发送了多少数据
n_packeterror = 0; % 有多少错误帧
n_packetdata = 0; % 发送了多少帧
for ii = 1:nloop
% 生成一帧数据,串并转换,并QPSK,生成一帧
frame_FDserial = rand(1,Nk*Nframe*M) > 0.5; % 发送的是bit
frame_FDparallel = reshape(frame_FDserial,Nk,Nframe*M);% 串并转换
frame_mod = QPSKMod(frame_FDparallel,Nk,Nframe); %调制
% IFFT
power_FT = sum(sum(abs(frame_mod).^2))/Nk/Nframe; % 计算下IFFT前的能量,FT表示频域
frame_mod_shift = ifftshift(frame_mod); % 频域归零
frame_ifft = ifft(frame_mod_shift, Nfft); % ifft
power_TD = sum(sum(abs(frame_ifft).^2))/Nk/Nframe; % 计算下IFFT前的能量,DT表示时域
% 添加保护间隔
frame_withGI = AddGI(frame_ifft, Nfft, NGI, Nframe, "CP"); % 添加保护间隔
% 并串转换
frame_TDserial = reshape(frame_withGI,1,Nsym*Nframe);
x=1:1:160;
% hold on;
% plot(x, frame_TDserial(1:160),'b');
% Channel
power_TDserial = sum(abs(frame_TDserial).^2)/Nsym/Nframe;
EsN0 = EbN0 + 10*log10(M); % 根据信噪比计算噪声能量,幅值,然后加在信号上
N0 = power_TDserial .* 10.^(-EsN0/10);
noise_msg = sqrt(N0 / 2) .* (randn(size(frame_TDserial)) + 1j * randn(size(frame_TDserial)));
frame_recieved = frame_TDserial + noise_msg;
% attn = sqrt(0.5*power_TDserial*SR/BR*10.^(-EbN0/10));
% noise_msg = attn .* (randn(size(frame_TDserial)) + 1j * randn(size(frame_TDserial)));
% frame_recieved = frame_TDserial + noise_msg;
% plot(x, noise_msg(1:160),'r');
% hold off;
% 接收端,串并转换
frame_recieved_parallel = reshape(frame_recieved,Nsym,Nframe);
% 去GI
frame_noGI = RemoveGI(frame_recieved_parallel, Nfft, NGI);
% FFT
frame_recieved_FD_shift = fft(frame_noGI, Nfft);
frame_recieved_FD = fftshift(frame_recieved_FD_shift);
% QPSK解调
frame_demod = QPSKDemod(frame_recieved_FD, Nk, Nframe);
% 并串转换
frame_output = reshape(frame_demod, 1, Nk*Nframe*M);
% 计算error
n_biterror_tmp = sum(abs(frame_output-frame_FDserial));
n_bitdata_tmp = length(frame_FDserial);
n_biterror = n_biterror + n_biterror_tmp;
n_bitdata = n_bitdata + n_bitdata_tmp;
if n_biterror_tmp ~= 0
n_packeterror = n_packeterror + 1;
end
n_packetdata = n_packetdata + 1;
end
% 计算在当前信噪比下的误码率
per = n_packeterror/n_packetdata;
ber = n_biterror/n_bitdata;
bers(kk)=ber;
fprintf('%f\t%e\t%e\t%d\t\n',EbN0,ber,per,nloop);
end
save("BERofdm.mat",'bers');
%% 画图
% bers = load("BERofdm.mat").bers;
awgn_theory = [0.0228784075610853,0.0125008180407376,0.00595386714777866,0.00238829078093281,0.000772674815378444,0.000190907774075993,3.36272284196176e-05,3.87210821552205e-06];
semilogy(EbN0s,awgn_theory,'-*',EbN0s,bers,'-+');
xlabel('比特信噪比');
ylabel('误码率');
title('不同信噪比下误码率仿真曲线');
legend('理论曲线','实验曲线');
这个代码主要有这么几点是值得注意的
简单来说,ifftshift是把矩阵做一个向左的长度为矩阵一半的循环位移,fftshift则是把矩阵做一个向右的长度为矩阵一半的循环位移。
当做ifft时,我们希望ifft窗对准的是 [ − N k 2 , N k 2 ] [-\frac{Nk}{2},\frac{Nk}{2}] [−2Nk,2Nk]这样一段信号,而我们把他放在matlab中进行仿真时,下标则是 [ 1 , N k ] [1,Nk] [1,Nk].因此,这就对应错了啊,因此我们要把前后倒置一下,这才满足理论上的要求。相应的,做完fft后,我们又要把结果恢复到 [ 1 , N k ] [1,Nk] [1,Nk],这样才方便和输入进行对比。
注意是顺序是先做ifftshift再做ifft,而接收端是先fft再做fftshift,原因上面已经说的很清楚啦。
想象一下FFT或是IFFT的公式.
IFFT中每一个 x [ n ] x[n] x[n] 是Nk个 1 N k X [ k ] \frac{1}{Nk}X[k] Nk1X[k]组成的,因此能量变为 1 N k 2 × N k = 1 N k \frac{1}{Nk^2}\times Nk=\frac{1}{Nk} Nk21×Nk=Nk1
FFT 中每一个 X [ k ] X[k] X[k]是Nfft个 x [ n ] x[n] x[n]组成的,因此能量变为 N f f t Nfft Nfft倍
能量是会产生变化的,而噪声是在信道时域中加上的,因此我们信噪比使用了时域的能量来计算。
https://blog.csdn.net/sinat_38151275/article/details/79869891可以参考这篇文章
总的来说,当经过数字调制后,一个符号包含了Npsk个比特那么
E
s
N
0
=
E
b
N
0
=
10
l
o
g
10
(
N
p
s
k
)
EsN0=EbN0=10log_{10}(Npsk)
EsN0=EbN0=10log10(Npsk)
https://zhuanlan.zhihu.com/p/281601152,陈老湿在这里做了很多探讨,不过我更偏向于将CP不看做冗余
因此我的代码是这样的
power_TDserial = sum(abs(frame_TDserial).^2)/Nsym/Nframe;
该部分代码来自工程的OFDM_fading.m
整个OFDM经历
生成一帧 → \rightarrow →串并转换 → \rightarrow →调制 → \rightarrow →ifftshift → \rightarrow →ifft → \rightarrow →添加CP → \rightarrow →并串转换 → \rightarrow →经历信道衰落,和信道卷积 → \rightarrow →计算能量,信噪比,添加AWGN → \rightarrow →接收端串并转换 → \rightarrow →去CP → \rightarrow →FFT → \rightarrow →FFTshift → \rightarrow → → \rightarrow →FFT信道矩阵h → \rightarrow →信道均衡 → \rightarrow →解调 → \rightarrow →并串转换
%-----------------------用TDL仿真多径衰落-------------------%
%-----------------------author:lzx-------------------------%
%-----------------------date:2022年3月25日-----------------%
%% 设置参数
clear;clc;
Nk = 128; % 子载波个数
Nfft = 128; % fft长度
Nframe = 6; % 一帧中有几个OFDM符号
Npsk = 2; % 调制符号所含比特
M = 2^Npsk; % 调制数
SR = 250000; % 符号速率
BR = SR .* Npsk; % 比特率
NGI = 32; % 保护间隔长度
EbN0s = 0:2:20; % 信噪比
Nsym = Nfft+NGI; % 系统长度
bers = zeros(1,length(EbN0s)); % 误码率储存数组
PowerTDL_dB = [0 -8 -17 -21 -25]; % TDL中信道抽头的功率,dB为单位
Delay = [0 3 5 6 8]; % TDL中信道时延
PowerTDL = 10.^(PowerTDL_dB/10); % TDL中信道抽头的功率
Nchannel=length(PowerTDL_dB); % 信道抽头数
Tau_maxTDL = Delay(end)+1; % 最大时延除以帧长,就是归一化的最大时延
fprintf('EbN0 \t \t ber\t\t\t per\t\t\t nloop \t\t \n');
%% 函数主体
for kk = 1:length(EbN0s)
% rng('default') % 初始化随机种子
EbN0 = EbN0s(kk);
nloop = 10000; % 发送多少帧
n_biterror = 0; % 错误的数据
n_bitdata = 0; % 一共发送了多少数据
n_packeterror = 0; % 有多少错误帧
n_packetdata = 0; % 发送了多少帧
for ii = 1:nloop
%--------------------------发射端-------------------------------%
% 生成一帧数据,串并转换,并QPSK,生成一帧
frame_FDserial = rand(1,Nk*Nframe*Npsk) > 0.5; % 发送的是bit
frame_FDparallel = reshape(frame_FDserial,Nk,Nframe*Npsk);% 串并转换
frame_mod = QPSKMod(frame_FDparallel,Nk,Nframe); %调制
% IFFT
power_FT = sum(sum(abs(frame_mod).^2))/Nk/Nframe; % 计算下IFFT前的能量,FT表示频域
frame_mod_shift = ifftshift(frame_mod); % 频域归零
frame_ifft = ifft(frame_mod_shift, Nfft); % ifft
% frame_ifft = ifft(frame_mod, Nfft);
power_TD = sum(sum(abs(frame_ifft).^2))/Nk/Nframe; % 计算下IFFT前的能量,DT表示时域
% 添加保护间隔
frame_withGI = AddGI(frame_ifft, Nfft, NGI, Nframe, "CP"); % 添加保护间隔
% 并串转换
frame_TDserial = reshape(frame_withGI,1,Nsym*Nframe);
% x=1:1:160;
% hold on;
% plot(x, frame_TDserial(1:160),'b');
%--------------------------Channel-------------------------------%
% 信号先经历衰落
channel = Rayleigh_model(Nchannel, PowerTDL);
h = zeros(1, Tau_maxTDL);
h(Delay+1) = channel;
frame_conv = conv(frame_TDserial, h);
frame_fading = frame_conv(:,1:length(frame_TDserial)); % 看似是线性卷积,实际上由于CP变成了循环卷积
% 添加高斯白噪声
power_TDserial = sum(abs(frame_TDserial).^2)/Nk/Nframe; % 计算出的能量和理论不符啊,没发现问题在哪
EsN0 = EbN0 + 10*log10(Npsk); % 根据信噪比计算噪声能量,幅值,然后加在信号上
N0 = power_TDserial .* 10.^(-EsN0/10);
noise_msg = sqrt(N0 / 2) .* (randn(size(frame_TDserial)) + 1j * randn(size(frame_TDserial)));
frame_recieved = frame_fading + noise_msg;
% 陈老湿方法添加高斯白噪声,本质上是一样的
% attn = sqrt(0.5*power_TDserial*SR/BR*10.^(-EbN0/10));
% noise_msg = attn .* (randn(size(frame_TDserial)) + 1j * randn(size(frame_TDserial)));
% frame_recieved = frame_TDserial + noise_msg;
% plot(x, noise_msg(1:160),'r');
% hold off;
%--------------------------接收端-------------------------------%
% 接收端,串并转换
frame_recieved_parallel = reshape(frame_recieved,Nsym,Nframe);
% 去GI
frame_noGI = RemoveGI(frame_recieved_parallel, Nfft, NGI);
% FFT
frame_recieved_FD_shift = fft(frame_noGI, Nfft);
frame_recieved_FD = fftshift(frame_recieved_FD_shift);
% frame_recieved_FD = fft(frame_noGI, Nfft);
% 信道均衡
H = fftshift(fft([h zeros(1, Nfft-Tau_maxTDL)].', Nfft));
frame_equalization = frame_recieved_FD ./ repmat(H, 1, Nframe);
% QPSK解调
frame_demod = QPSKDemod(frame_equalization, Nk, Nframe);
% 并串转换
frame_output = reshape(frame_demod, 1, Nk*Nframe*Npsk);
% 计算error
n_biterror_tmp = sum(abs(frame_output-frame_FDserial));
n_bitdata_tmp = length(frame_FDserial);
n_biterror = n_biterror + n_biterror_tmp;
n_bitdata = n_bitdata + n_bitdata_tmp;
if n_biterror_tmp ~= 0
n_packeterror = n_packeterror + 1;
end
n_packetdata = n_packetdata + 1;
end
% 计算在当前信噪比下的误码率
per = n_packeterror/n_packetdata;
ber = n_biterror/n_bitdata;
bers(kk)=ber;
fprintf('%f\t%e\t%e\t%d\t\n',EbN0,ber,per,nloop);
end
save("BERofdm_rayleigh.mat",'bers');
%% 画图
% bers = load(!"BERofdm_rayleigh.mat").bers;
rayleigh_theory = 0.5.*(1-(1-(1./(10.^(EbN0s./10).*(48/80)+1))).^0.5);
semilogy(EbN0s,rayleigh_theory,'-*',EbN0s,bers,'-+');
xlabel('比特信噪比');
ylabel('误码率');
title('不同信噪比下误码率仿真曲线');
legend('理论曲线','实验曲线');
我们把信道卷积的代码拿出来看一下
PowerTDL_dB = [0 -8 -17 -21 -25]; % TDL中信道抽头的功率,dB为单位
Delay = [0 3 5 6 8]; % TDL中信道时延
PowerTDL = 10.^(PowerTDL_dB/10); % TDL中信道抽头的功率
Nchannel=length(PowerTDL_dB); % 信道抽头数
Tau_maxTDL = Delay(end)+1; % 最大时延除以帧长,就是归一化的最大时延
% 信号先经历衰落
channel = Rayleigh_model(Nchannel, PowerTDL);
h = zeros(1, Tau_maxTDL);
h(Delay+1) = channel;
frame_conv = conv(frame_TDserial, h);
frame_fading = frame_conv(:,1:length(frame_TDserial)); % 看似是线性卷积,实际上由于CP变成了循环卷积
% 信道均衡
H = fftshift(fft([h zeros(1, Nfft-Tau_maxTDL)].', Nfft));
frame_equalization = frame_recieved_FD ./ repmat(H, 1, Nframe);
这里其实就涵盖了所有第二个仿真和第一个仿真的区别了,这个代码主要有这么几点是值得注意的
TDL模型里只包含两个内容,第一个是信道对功率的变化,这是用瑞利分布乘以功率来完成的。第二个是时延, 这是用信道的长度和线性卷积来完成的。我们看到代码中有一项Tau_maxTDL = Delay(end)+1; 这一般被称作信道长度。就是最大时延在TDL这个单位时延下的长度嘛。这样就能理解TDL是如何表达瑞利信道的了。
我们知道,在FFT中,循环卷积才是真正的卷积,各种性质都是在循环卷积中证明完成的,这其中当然包括大名鼎鼎的时域的卷积等于频域的点乘,也就是说,时域的循环卷积核频域的点乘结果是相同的。可是信道和信号在时域发生的实际上是线性卷积,这就给我们均衡的时候制造困难了,没有简便的算法啊!
我们在DSP课程中学过用循环卷积去计算线性卷积,方法就是把循环卷积的L扩展到M+N-1时候计算。那么有没有方法可以把线性卷积转化为循环卷积呢?当然有!这就是CP
具体原理可以看https://blog.51cto.com/u_15127585/2669966
这是非常巧妙的



也就是说CP有两个作用
https://tieba.baidu.com/p/5402720283?red_tag=0784237449
这居然也被我查到了
这正是TDL想要的效果,我们多出来的实际上就是TDL产生的最大时延/ T s T_s Ts
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO
我正在开发一个Rails应用程序,我需要在其中找到给定特定偏移量或时区的夏令时开始和结束日期。我基本上在我的数据库中保存了从用户浏览器接收到的时区偏移量(“+3”,“-5”),我想在它出现时修改它由于夏令时的变化。我知道Time实例变量有dst?和isdst方法,如果存储在它们中的日期在夏令时与否。>Time.new.isdst=>true但是使用它来查找夏令时的开始和结束日期会占用太多资源,而且我还必须为我拥有的每个时区偏移量执行此操作。我想知道更好的方法。 最佳答案 好的,基于你所说的和@dhouty'sanswer:您希望能够
我有一台生产机器和一台开发机器,都运行ubuntu8.10并且都运行最新的phusionpassenger。当我在osx上的本地开发机器上使用ruby1.9.1时,我想知道外面的人是否已经在使用带有ruby1.9.1甚至1.9.2的phusionpassenger?如果是这样,请告诉我们您的设置!此外,有没有办法在apache上使用phusionpassenger同时运行ruby1.8.7(ree)和1.9.1?感谢您的指点,我在任何地方都找不到任何提示... 最佳答案 是的,从某些2.2.x版本开始就正式支持它,我不记
date_select方法只能设置:start_year,但我想设置开始日期(例如3个月前的日期)(但没有这样的选项)。那么,我可以将开始日期设置为date_select方法吗?或者,要制作这样的选择框,我应该使用select_tag和options_for_select吗?或者,有什么解决办法吗?谢谢, 最佳答案 有可能……例如:start_year–设置年份选择的开始年份。默认为Time.now.year-5参见thisresource. 关于ruby-Rails3-我可以将开始日期
我想从特定索引开始遍历数组。我该怎么做?myj.eachdo|temp|...end 最佳答案 执行以下操作:your_array[your_index..-1].eachdo|temp|###end 关于ruby-从特定索引开始迭代数组,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/44151758/
目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标
前面一篇关于智能合约翻译文讲到了,是一种计算机程序,既然是程序,那就可以使用程序语言去编写智能合约了。而若想玩区块链上的项目,大部分区块链项目都是开源的,能看得懂智能合约代码,或找出其中的漏洞,那么,学习Solidity这门高级的智能合约语言是有必要的,当然,这都得在公链``````以太坊上,毕竟国内的联盟链有些是不兼容Solidity。Solidity是一种面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态下的账户行为的程序。Solidity是运行在以太坊(Ethereum)虚拟机(EVM)上,其语法受到了c++、python、javascript影响。Solidity是静态类型
一、机器人介绍 此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接
我有一些使用delayed_job的小程序。在我的本地主机上一切正常,但是当我将我的应用程序部署到Heroku并单击应该由delayed_job执行的链接时,没有任何反应,“任务”只是保存到表delayed_job中。Inthisarticleonherokublog写入时,执行delayed_job表中的任务,当运行此命令时rakejobs:work。但是我怎样才能运行这个命令呢?命令应该放在哪里?在代码中,还是从终端控制台? 最佳答案 如果您正在运行Cedar堆栈,请从终端控制台运行以下命令:herokurunrakejobs: