初识OFDM(七):OFDM中的信道估计

零.代码地址

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");

二. 代码分析

1. 如何插入导频

此处的导频和之前文章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:npilot1).lpilot0,others
但是,信道估计中使用的是梳状导频,他在时间上是连续的,在频域上离散。也就是说导频占用了一些子载波资源,用于信道估计。而CFO用的则是前导,他利用的是离散的频域再时域的重复性去估计CFO。当然信道也可以用前导去估计,不过我这里是每一个OFDM符号都去更新一次的,后续如果需要,也可以改为前导去估计。

image-20220329231145992

2. LS估计中的一些注意事项

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)<length_H
        slope = (input_pilot(end)-input_pilot(end-1))/(indexs_pilot(end)-indexs_pilot(end-1));
        tail_compensation(:, kk) = input_pilot(end)+slope*(length_H-indexs_pilot(end));
    end
end
if indexs_pilot(1)>1
    indexs_pilot_compensation = [1 indexs_pilot];
    output_pilot = [head_compensation; input_pilot];
end
if  indexs_pilot(end)<length_H
    indexs_pilot_compensation= [indexs_pilot length_H];
    output_pilot = [input_pilot; tail_compensation];
end
end

这样就得到了LS估计的结果

3. DFT-LS估计算法

据说DFT-LS估计算法是5G中使用的信道估计算法,当然是很值得学习一下的啦。其实他的思路很简单

image-20220329232157345

就是说H是一个带限的滤波器,而引入的高斯噪声则是一个通带白噪声,那我们就亏了啊。因此我们用一个低通滤波器把噪声也滤成一个带限白噪声。因此我们做IDFT,去除掉最大时延后的噪声,在做DFT获得带限白噪声的信道估计结果,这样的效果肯定是比LS估计好的

注意此时我们已经引入了先验条件,信道长度即最大时延,这个最大时延如何获得?应该是观察结果,并不一定需要知道h,这非常重要,这个思想也被用于MMSE信道估计

4. 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的估计结果。

image-20220329235944054

这计算量太大的,怎么办呢?把信号改为信号的能量,这就是LMMSE

最终公式是这样的

image-20220329234932804

我的代码正是3利用这个公式来计算的

注意,我们又引入了先验条件 R h h R_{hh} Rhh,如何获得?不好获得,这大概就是MMSE信道估计应用不广泛的原因吧。

三.总结

由于MIMO-OFDM这本书中MMSE估计使用了h,我纠结了很久,你都知道了h还估计个什么?但后来书中一段话点醒了我

image-20220330000528641

想要准,肯定要知道一些东西的。另外https://zhuanlan.zhihu.com/p/358985352作者也在评论区指出

image-20220330000631007

哈哈哈看来大家都对此有疑问啊,不过MMSE信道估计本身应用就不广泛,这就是原因吧。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐