https://github.com/liu-zongxi/OFDM_simulation
代码参考了https://zhuanlan.zhihu.com/p/385096476陈老湿的仿真,但各个函数我都有重新实现,希望写的更规范一些
%-----------------------导频和信道均衡----------------------%
%-----------------------author:lzx-------------------------%
%-----------------------date:2022年3月27日21:20:22----------%
%% 设置参数
clear;clc;
% OFDM相关参数
Nk = 128; % 子载波个数
Nused = 96; % 数据个数,剩下来的留给导频了
Nfft = 128; % fft长度
Nframe = 6; % 一帧中有几个OFDM符号
Npsk = 2; % 调制符号所含比特
M = 2^Npsk; % 调制数
SR = 250000; % 符号速率
BR = SR .* Npsk; % 比特率
NGI = 32; % 保护间隔长度
Nsym = Nfft+NGI; % 系统长度
% 信噪比相关参数
EbN0s = 1:1:50; % 信噪比
bers_perfect = zeros(1,length(EbN0s)); % 误码率储存数组
bers_LS = zeros(1,length(EbN0s)); % 误码率储存数组
bers_DFT = zeros(1,length(EbN0s)); % 误码率储存数组
bers_MMSE = 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; % 最大时延除以帧长,就是归一化的最大时延
% 导频信息
L_pilot = 4; % 导频间隔
start_pilot = 1; % 导频起始位置
Npilot = Nk/L_pilot; % 导频数量
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_perfect = 0; % 错误的数据
n_biterror_LS = 0; % 错误的数据
n_biterror_DFT = 0; % 错误的数据
n_biterror_MMSE = 0; % 错误的数据
n_bitdata = 0; % 一共发送了多少数据
n_packeterror_perfect = 0; % 有多少错误帧
n_packeterror_LS = 0; % 有多少错误帧
n_packeterror_DFT = 0; % 有多少错误帧
n_packeterror_MMSE = 0; % 有多少错误帧
n_packetdata = 0; % 发送了多少帧
for ii = 1:nloop
%--------------------------发射端-------------------------------%
% 生成一帧数据,串并转换,并QPSK,生成一帧
frame_FDserial = rand(1,Nused*Nframe*Npsk) > 0.5; % 发送的是bit
frame_FDparallel = reshape(frame_FDserial,Nused,Nframe*Npsk);% 串并转换
frame_mod = QPSKMod(frame_FDparallel,Nused,Nframe); %调制
% 插入导频,梳状导频频率上离散,时间上延续
[X_pilot, frame_with_pilot] = AddPilot(frame_mod, L_pilot, start_pilot, 1, Npilot, Nused, Nframe);
% IFFT
% power_FT = sum(sum(abs(frame_mod).^2))/Nk/Nframe; % 计算下IFFT前的能量,FT表示频域
frame_mod_shift = ifftshift(frame_with_pilot); % 频域归零
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)/Nsym/Nframe; % 计算出的能量和理论不符啊,没发现问题在哪
EsN0 = EbN0 + 10*log10(Npsk) + 10*log10(Nused/Nsym); % 根据信噪比计算噪声能量,幅值,然后加在信号上
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));
Rhh = H*H';
H_LS_linear = ChannelEstimation_LS(frame_recieved_FD, X_pilot, Npilot, L_pilot, start_pilot, Nused, Nframe);
H_MMSE = ChannelEstimation_MMSE(H_LS_linear, Rhh, Nused, Npilot, Nframe, EbN0);
% 做DFT滤除噪声
h_LS = ifft(ifftshift(H_LS_linear),Nfft);
h_LS_DFT = h_LS(1:Tau_maxTDL, :);
H_LS_DFT = fftshift(fft(h_LS_DFT, Nfft));
% 信道均衡
frame_equalization_perfect = frame_recieved_FD ./ repmat(H, 1, Nframe);
frame_equalization_LS = frame_recieved_FD ./ H_LS_linear;
frame_equalization_DFT = frame_recieved_FD ./ H_LS_DFT;
frame_equalization_MMSE = frame_recieved_FD ./ H_MMSE;
% 去除导频
frame_no_pilot_perfect = RemovePilot(frame_equalization_perfect, L_pilot, start_pilot, Npilot);
frame_no_pilot_LS = RemovePilot(frame_equalization_LS, L_pilot, start_pilot, Npilot);
frame_no_pilot_DFT = RemovePilot(frame_equalization_DFT, L_pilot, start_pilot, Npilot);
frame_no_pilot_MMSE = RemovePilot(frame_equalization_MMSE, L_pilot, start_pilot, Npilot);
% QPSK解调
frame_demod_perfect = QPSKDemod(frame_no_pilot_perfect, Nused, Nframe);
frame_demod_LS = QPSKDemod(frame_no_pilot_LS, Nused, Nframe);
frame_demod_DFT = QPSKDemod(frame_no_pilot_DFT, Nused, Nframe);
frame_demod_MMSE = QPSKDemod(frame_no_pilot_MMSE, Nused, Nframe);
% 并串转换
frame_output_perfect = reshape(frame_demod_perfect, 1, Nused*Nframe*Npsk);
frame_output_LS = reshape(frame_demod_LS, 1, Nused*Nframe*Npsk);
frame_output_DFT = reshape(frame_demod_DFT, 1, Nused*Nframe*Npsk);
frame_output_MMSE = reshape(frame_demod_MMSE, 1, Nused*Nframe*Npsk);
% 计算error
n_biterror_perfect_tmp = sum(abs(frame_output_perfect-frame_FDserial));
n_biterror_LS_tmp = sum(abs(frame_output_LS-frame_FDserial));
n_biterror_DFT_tmp = sum(abs(frame_output_DFT-frame_FDserial));
n_biterror_MMSE_tmp = sum(abs(frame_output_MMSE-frame_FDserial));
n_bitdata_tmp = length(frame_FDserial);
n_biterror_perfect = n_biterror_perfect + n_biterror_perfect_tmp;
n_biterror_LS = n_biterror_LS + n_biterror_LS_tmp;
n_biterror_DFT = n_biterror_DFT + n_biterror_DFT_tmp;
n_biterror_MMSE = n_biterror_MMSE + n_biterror_MMSE_tmp;
n_bitdata = n_bitdata + n_bitdata_tmp;
if n_biterror_perfect_tmp ~= 0
n_packeterror_perfect = n_packeterror_perfect + 1;
end
if n_biterror_LS_tmp ~= 0
n_packeterror_LS = n_packeterror_LS + 1;
end
if n_biterror_DFT_tmp ~= 0
n_packeterror_DFT = n_packeterror_DFT + 1;
end
if n_biterror_MMSE_tmp ~= 0
n_packeterror_MMSE = n_packeterror_MMSE + 1;
end
n_packetdata = n_packetdata + 1;
end
% 计算在当前信噪比下的误码率
per_perfect = n_packeterror_perfect/n_packetdata;
per_LS = n_packeterror_LS/n_packetdata;
per_DFT = n_packeterror_DFT/n_packetdata;
per_MMSE = n_packeterror_MMSE/n_packetdata;
ber_perfect = n_biterror_perfect/n_bitdata;
ber_LS = n_biterror_LS/n_bitdata;
ber_DFT = n_biterror_DFT/n_bitdata;
ber_MMSE = n_biterror_MMSE/n_bitdata;
bers_perfect(kk)=ber_perfect;
bers_LS(kk)=ber_LS;
bers_DFT(kk)=ber_DFT;
bers_MMSE(kk)=ber_MMSE;
fprintf('%f\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%e\t%d\t\n',EbN0,ber_perfect,ber_LS,ber_DFT, ber_MMSE, per_perfect,per_LS,per_DFT, per_MMSE, nloop);
end
save("BERofdm_perfect.mat",'bers_perfect');
save("BERofdm_LS.mat",'bers_LS');
save("BERofdm_DFT.mat",'bers_DFT');
save("BERofdm_MMSE.mat",'bers_MMSE');
%% 画图
% bers = load(!"BERofdm_rayleigh.mat").bers;
EbN0s = 1:1:50; % 信噪比
bers_perfect = load("BERofdm_perfect.mat").bers_perfect;
bers_LS = load("BERofdm_LS.mat").bers_LS;
bers_DFT = load("BERofdm_DFT.mat").bers_DFT;
bers_MMSE = load("BERofdm_MMSE.mat").bers_MMSE;
rayleigh_theory = 0.5.*(1-(1-(1./(10.^(EbN0s./10).*(96/128)+1))).^0.5);
semilogy(EbN0s,rayleigh_theory,'-*',EbN0s,bers_perfect,'-+',EbN0s,bers_LS,'-^', EbN0s,bers_DFT,'->', EbN0s,bers_MMSE,'-<');
xlabel('比特信噪比');
ylabel('误码率');
title('不同信噪比下误码率仿真曲线');
legend('理论曲线','perfect', "LS", "LS-DFT", "MMSE");
此处的导频和之前文章CFO所用的导频是不一样的,单从导频的角度来看,两者好像是一样的,都是
X p i l o t [ k ] = { 1 , k = s t a r t p i l o t + ( 0 : n p i l o t − 1 ) . ∗ l p i l o t 0 , o t h e r s X_{pilot}[k] = \left\{\begin{aligned}&1,\quad k=startpilot+(0:npilot-1).*lpilot \\ &0,\quad others\end{aligned}\right. Xpilot[k]={1,k=startpilot+(0:npilot−1).∗lpilot0,others
但是,信道估计中使用的是梳状导频,他在时间上是连续的,在频域上离散。也就是说导频占用了一些子载波资源,用于信道估计。而CFO用的则是前导,他利用的是离散的频域再时域的重复性去估计CFO。当然信道也可以用前导去估计,不过我这里是每一个OFDM符号都去更新一次的,后续如果需要,也可以改为前导去估计。
LS估计实际上是非常好理解的,他很符合我们人类的直觉。如何估计信道?用输出除以输入就可以了。
LS的具体计算可以参考我师兄写的这篇博客https://blog.csdn.net/uekaikai/article/details/120217359?spm=1001.2014.3001.5502
我们的代码中实现LS的思路是首先计算出有导频的估计结果,然后再插值。插值我们使用了matlab自带的interp1函数,但这个函数插值只能插中间的,如果头尾没有数值,他就无能为力了,所以我们要手动把首尾都填充好,这样才方便interp1函数的调用
%------------------补偿导频估计的头尾,便于插值---------------%
%-----------------------author:lzx-------------------------%
%-----------------------date:2022年3月28日-----------------%
function [output_pilot, indexs_pilot_compensation] = InterpolateCompensation(input_pilot, indexs_pilot, length_H, nframe)
% 输入
% input_pilot: (Nused, Nframe),输入导频信号
% indexs_pilot:导频所在的位置
% length_H:需要补偿的长度,也就是Nused+Npilot
% nframe: 一个发送多少个OFDM符号
% 输出
% output_pilot:(Npilot+1, Nframe)补偿了头或者尾的导频
% indexs_pilot_compensation: 补偿了头尾的导频位置
head_compensation = zeros(1, nframe);
tail_compensation = zeros(1, nframe);
for kk = 1:nframe
% 如果前面有空缺,先补偿头,就是第一个数
if indexs_pilot(1)>1
slope = (input_pilot(2, kk)-input_pilot(1, kk))/(indexs_pilot(2)-indexs_pilot(1));
head_compensation(:, kk) = input_pilot(1, kk)-slope*(indexs_pilot(1, kk)-1);
end
% 尾部空缺再补偿尾,就是最后一个数
if indexs_pilot(end)1
indexs_pilot_compensation = [1 indexs_pilot];
output_pilot = [head_compensation; input_pilot];
end
if indexs_pilot(end)
这样就得到了LS估计的结果
据说DFT-LS估计算法是5G中使用的信道估计算法,当然是很值得学习一下的啦。其实他的思路很简单
就是说H是一个带限的滤波器,而引入的高斯噪声则是一个通带白噪声,那我们就亏了啊。因此我们用一个低通滤波器把噪声也滤成一个带限白噪声。因此我们做IDFT,去除掉最大时延后的噪声,在做DFT获得带限白噪声的信道估计结果,这样的效果肯定是比LS估计好的
注意此时我们已经引入了先验条件,信道长度即最大时延,这个最大时延如何获得?应该是观察结果,并不一定需要知道h,这非常重要,这个思想也被用于MMSE信道估计
这是我花了最多时间去研究的了,花了整整两天。首先要明确MMSE是一个在通信中应用非常广泛的技术,除了信道估计外,他还被用于信道均衡,检测(这俩是一个东西?目前还不会)。很多帖子是研究信道检测MMSE的,大家要注意区分。
MMSE本质上的思路是通过最小化MSE得到一个加权矩阵W,让Wy去逼近x,在信道估计里,y是接收到的信号,x就是信道H,在这种假设下,计算我们需要的 W M M S E W_{MMSE} WMMSE有两种方法,一种是矩阵求导,一种是利用正交性原理,但总之结果是一样的,具体的推导过程可以参考这篇文章,目前我还没有完全弄清楚
https://blog.csdn.net/qq_37989552/article/details/102946707
既然要最小化MSE,而MSE是H和Wy的函数,MSE计算肯定是涉及H的。但H我们不知道啊!没关系,经过一系列的推导,我们只需要用 R h h R_{hh} Rhh就行了, R h h R_{hh} Rhh如何获得呢?应该是统计概念,这里我也没有搞清楚,不过我们这里就采用H直接来计算 R h h R_{hh} Rhh啦。==Rhh本应该是一个期望,是一个统计量,但我是通过H准确计算的,所以我们的估计结果非常好。==另外呢因为Y=HX+N,我们肯定可以把输入信号X和噪声引入进来。利用Y和X,我们就可以用LS的估计结果 H L S H_{LS} HLS来代替X和Y。
最终的估计结果呢,和四个东西有关,真实信道的自相关矩阵 R h h R_{hh} Rhh,信号,噪声的方差以及H_LS的估计结果。
这计算量太大的,怎么办呢?把信号改为信号的能量,这就是LMMSE
最终公式是这样的
我的代码正是3利用这个公式来计算的
注意,我们又引入了先验条件 R h h R_{hh} Rhh,如何获得?不好获得,这大概就是MMSE信道估计应用不广泛的原因吧。
由于MIMO-OFDM这本书中MMSE估计使用了h,我纠结了很久,你都知道了h还估计个什么?但后来书中一段话点醒了我
想要准,肯定要知道一些东西的。另外https://zhuanlan.zhihu.com/p/358985352作者也在评论区指出
哈哈哈看来大家都对此有疑问啊,不过MMSE信道估计本身应用就不广泛,这就是原因吧。